@arms/rum-reactnative 0.1.3-beta.2 → 0.1.3-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- 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/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/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 +3 -2
- package/src/NativeRumSdkReactnative.ts +107 -0
package/README.md
CHANGED
|
@@ -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 不强制特定版本)
|
|
@@ -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.4"};
|
|
18
18
|
const VERSION = exports.VERSION = _pkg.version;
|
|
19
19
|
|
|
20
20
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -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.4"};
|
|
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.4",
|
|
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",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"lib",
|
|
24
|
+
"src/NativeRumSdkReactnative.ts",
|
|
24
25
|
"android",
|
|
25
26
|
"ios",
|
|
26
27
|
"docs",
|
|
@@ -107,7 +108,7 @@
|
|
|
107
108
|
"typescript": "^5.8.3"
|
|
108
109
|
},
|
|
109
110
|
"peerDependencies": {
|
|
110
|
-
"@arms/rum-core": "
|
|
111
|
+
"@arms/rum-core": "0.1.7",
|
|
111
112
|
"react": "*",
|
|
112
113
|
"react-native": ">=0.68.0"
|
|
113
114
|
},
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
import { NativeModules, TurboModuleRegistry } from 'react-native';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 原生设备信息,对应存储模型中的公共属性:
|
|
6
|
+
* - device.* (id / type / brand / model / name)
|
|
7
|
+
* - os.* (type / version / container / container_version)
|
|
8
|
+
* - app.package
|
|
9
|
+
* - viewport.width / viewport.height
|
|
10
|
+
*/
|
|
11
|
+
export interface NativeDeviceInfo {
|
|
12
|
+
device_id?: string;
|
|
13
|
+
device_type?: string;
|
|
14
|
+
device_brand?: string;
|
|
15
|
+
device_model?: string;
|
|
16
|
+
device_name?: string;
|
|
17
|
+
os_type?: string;
|
|
18
|
+
os_version?: string;
|
|
19
|
+
app_package?: string;
|
|
20
|
+
app_version?: string;
|
|
21
|
+
viewport_width?: number;
|
|
22
|
+
viewport_height?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface Spec extends TurboModule {
|
|
26
|
+
getItem(key: string): Promise<string | null>;
|
|
27
|
+
setItem(key: string, value: string): Promise<void>;
|
|
28
|
+
removeItem(key: string): Promise<void>;
|
|
29
|
+
clear(): Promise<void>;
|
|
30
|
+
getAllKeys(): Promise<string[]>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 采集原生平台设备信息,仅在初始化时调用一次。
|
|
34
|
+
*/
|
|
35
|
+
getDeviceInfo(): Promise<NativeDeviceInfo>;
|
|
36
|
+
|
|
37
|
+
// === 崩溃采集相关 ===
|
|
38
|
+
|
|
39
|
+
/** 获取待上报的崩溃报告列表(下次启动时调用) */
|
|
40
|
+
getPendingCrashes(): Promise<string>;
|
|
41
|
+
|
|
42
|
+
/** 清除已上报的崩溃文件 */
|
|
43
|
+
clearCrashReports(crashIds: string): Promise<void>;
|
|
44
|
+
|
|
45
|
+
/** 清除所有崩溃报告文件(用于清理无 crash_id 的旧版文件) */
|
|
46
|
+
clearAllCrashReports(): Promise<void>;
|
|
47
|
+
|
|
48
|
+
/** 清理过期和超限的崩溃文件(maxFiles 上限,maxAgeMs 过期时间) */
|
|
49
|
+
cleanupOldCrashes(maxFiles: number, maxAgeMs: number): Promise<void>;
|
|
50
|
+
|
|
51
|
+
/** 查询上次运行是否发生过崩溃 */
|
|
52
|
+
crashedLastRun(): Promise<boolean>;
|
|
53
|
+
|
|
54
|
+
/** 更新崩溃上下文快照(session/view 变更时调用) */
|
|
55
|
+
updateCrashContext(context: string): void;
|
|
56
|
+
|
|
57
|
+
/** 触发测试崩溃(仅用于开发调试,调用后 App 立即崩溃) */
|
|
58
|
+
triggerTestCrash(crashType: string): void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 获取原生模块。
|
|
63
|
+
*
|
|
64
|
+
* 优先使用 TurboModuleRegistry(RN 0.68+ TurboModule 架构),
|
|
65
|
+
* 若未找到则降级到 Legacy Bridge 的 NativeModules(RN 0.68-0.70 未开启 New Arch 时),
|
|
66
|
+
* 若均不可用则返回一组空实现兜底。
|
|
67
|
+
*
|
|
68
|
+
* 使用非强制的 `TurboModuleRegistry.get`(而非 `getEnforcing`),避免在原生
|
|
69
|
+
* 二进制未注册该模块时于「模块加载阶段」直接抛异常——那会连锁导致整个 JS
|
|
70
|
+
* bundle 评估失败(表现为所有路由丢失 default export、App 白屏)。
|
|
71
|
+
*
|
|
72
|
+
* 当原生模块缺失时(通常是新增原生代码后未重新编译原生 App,只重跑了 Metro),
|
|
73
|
+
* 返回一组空实现兜底:崩溃采集与原生设备信息降级为不可用,但 PV / 行为 / API /
|
|
74
|
+
* JS 异常等纯 JS 能力不受影响。需重新构建原生工程(如 `expo run:ios` /
|
|
75
|
+
* `pod install` + 重新编译)后崩溃采集才会真正生效。
|
|
76
|
+
*/
|
|
77
|
+
const nativeModule =
|
|
78
|
+
TurboModuleRegistry.get<Spec>('RumSdkReactnative') ??
|
|
79
|
+
(NativeModules.RumSdkReactnative as Spec | undefined);
|
|
80
|
+
|
|
81
|
+
if (!nativeModule) {
|
|
82
|
+
console.warn(
|
|
83
|
+
"[rum] Native module 'RumSdkReactnative' not found in the native binary. " +
|
|
84
|
+
'Crash reporting and native device info are disabled. ' +
|
|
85
|
+
'Rebuild the native app (e.g. `expo run:ios` / `expo run:android`, ' +
|
|
86
|
+
'or `pod install` + rebuild) so the module gets registered.'
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 原生模块缺失时的空实现,保证调用方不会因缺方法而抛错。 */
|
|
91
|
+
const fallback: Spec = {
|
|
92
|
+
getItem: async () => null,
|
|
93
|
+
setItem: async () => {},
|
|
94
|
+
removeItem: async () => {},
|
|
95
|
+
clear: async () => {},
|
|
96
|
+
getAllKeys: async () => [],
|
|
97
|
+
getDeviceInfo: async () => ({}),
|
|
98
|
+
getPendingCrashes: async () => '[]',
|
|
99
|
+
clearCrashReports: async () => {},
|
|
100
|
+
clearAllCrashReports: async () => {},
|
|
101
|
+
cleanupOldCrashes: async () => {},
|
|
102
|
+
crashedLastRun: async () => false,
|
|
103
|
+
updateCrashContext: () => {},
|
|
104
|
+
triggerTestCrash: () => {},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export default nativeModule ?? fallback;
|