@arms/rum-reactnative 0.1.1 → 0.1.3-beta.0
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 +399 -15
- package/RumSdkReactnative.podspec +28 -0
- package/android/build.gradle +83 -0
- package/android/gradle.properties +6 -0
- package/android/src/main/AndroidManifest.xml +13 -0
- package/android/src/main/java/com/rumsdkreactnative/RumSdkReactnativeModule.kt +512 -0
- package/android/src/main/java/com/rumsdkreactnative/RumSdkReactnativePackage.kt +33 -0
- package/android/src/main/java/com/rumsdkreactnative/crash/CrashContextManager.kt +157 -0
- package/android/src/main/java/com/rumsdkreactnative/crash/CrashHandler.kt +444 -0
- package/android/src/main/java/com/rumsdkreactnative/crash/CrashInitProvider.kt +50 -0
- package/android/src/main/java/com/rumsdkreactnative/crash/NdkCrashHandler.kt +66 -0
- package/android/src/main/jni/CMakeLists.txt +21 -0
- package/android/src/main/jni/rum_backtrace.c +144 -0
- package/android/src/main/jni/rum_crash_handler.c +200 -0
- package/android/src/main/jni/rum_crash_writer.c +442 -0
- package/android/src/main/jni/rum_module_info.c +316 -0
- package/android/src/main/jni/rum_module_info.h +54 -0
- package/android/src/main/jni/rum_test_crash.cpp +55 -0
- package/app.plugin.js +11 -0
- package/docs/ReactNative SDK/351/205/215/347/275/256/345/217/202/350/200/203.md" +671 -0
- package/docs/SDK /347/211/210/346/234/254/350/257/264/346/230/216.md" +12 -0
- package/docs//346/216/245/345/205/245ReactNative/345/272/224/347/224/250.md +238 -0
- package/ios/PrivacyInfo.xcprivacy +93 -0
- package/ios/RumCrashReporter.h +38 -0
- package/ios/RumCrashReporter.mm +861 -0
- package/ios/RumSdkReactnative.h +5 -0
- package/ios/RumSdkReactnative.mm +291 -0
- package/ios/fishhook/fishhook.c +264 -0
- package/ios/fishhook/fishhook.h +76 -0
- package/lib/commonjs/NativeRumSdkReactnative.js +50 -0
- package/lib/commonjs/collector/action/index.js +108 -0
- package/lib/commonjs/collector/exception/crash.js +148 -0
- package/lib/commonjs/collector/exception/index.js +238 -0
- package/lib/commonjs/collector/index.js +42 -0
- package/lib/commonjs/collector/resource/api.js +286 -0
- package/lib/commonjs/collector/view/perf.js +79 -0
- package/lib/commonjs/collector/view/pv.js +88 -0
- package/lib/commonjs/expoConfigPlugin.js +37 -0
- package/lib/commonjs/index.js +29 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/plugin.js +47 -0
- package/lib/commonjs/processor/default-processor.js +27 -0
- package/lib/commonjs/processor/session-processor.js +47 -0
- package/lib/commonjs/reporter/index.js +148 -0
- package/lib/commonjs/reporter/monitor-reporter.js +43 -0
- package/lib/commonjs/shell.js +91 -0
- package/lib/commonjs/types/client.js +6 -0
- package/lib/commonjs/types/crash.js +37 -0
- package/lib/commonjs/types/index.js +2 -0
- package/lib/commonjs/utils/action.js +305 -0
- package/lib/commonjs/utils/api.js +83 -0
- package/lib/commonjs/utils/base.js +95 -0
- package/lib/commonjs/utils/crash.js +146 -0
- package/lib/commonjs/utils/platform.js +162 -0
- package/lib/commonjs/utils/session.js +158 -0
- package/lib/commonjs/utils/url.js +59 -0
- package/lib/commonjs/utils/view.js +93 -0
- package/lib/module/NativeRumSdkReactnative.js +47 -0
- package/lib/module/collector/action/index.js +45 -10
- package/lib/module/collector/exception/crash.js +143 -0
- package/lib/module/collector/exception/index.js +144 -46
- package/lib/module/collector/index.js +8 -0
- package/lib/module/collector/resource/api.js +6 -6
- package/lib/module/collector/view/pv.js +13 -2
- package/lib/module/expoConfigPlugin.js +33 -0
- package/lib/module/index.js +1 -0
- package/lib/module/plugin.js +4 -1
- package/lib/module/processor/session-processor.js +22 -0
- package/lib/module/reporter/index.js +102 -16
- package/lib/module/reporter/monitor-reporter.js +38 -0
- package/lib/module/shell.js +32 -7
- package/lib/module/types/crash.js +33 -0
- package/lib/module/utils/action.js +299 -0
- package/lib/module/utils/api.js +10 -10
- package/lib/module/utils/base.js +9 -9
- package/lib/module/utils/crash.js +141 -0
- package/lib/module/utils/platform.js +127 -72
- package/lib/module/utils/session.js +23 -4
- package/lib/module/utils/view.js +2 -1
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/src/NativeRumSdkReactnative.d.ts +49 -0
- package/lib/typescript/{src → commonjs/src}/collector/action/index.d.ts +7 -1
- package/lib/typescript/commonjs/src/collector/exception/crash.d.ts +28 -0
- package/lib/typescript/{src → commonjs/src}/collector/exception/index.d.ts +7 -0
- package/lib/typescript/commonjs/src/collector/index.d.ts +6 -0
- package/lib/typescript/{src → commonjs/src}/collector/view/pv.d.ts +2 -2
- package/lib/typescript/commonjs/src/expoConfigPlugin.d.ts +28 -0
- package/lib/typescript/commonjs/src/index.d.ts +6 -0
- package/lib/typescript/{src → commonjs/src}/processor/session-processor.d.ts +2 -0
- package/lib/typescript/{src → commonjs/src}/reporter/index.d.ts +3 -1
- package/lib/typescript/commonjs/src/reporter/monitor-reporter.d.ts +8 -0
- package/lib/typescript/commonjs/src/shell.d.ts +57 -0
- package/lib/typescript/{src → commonjs/src}/types/client.d.ts +2 -2
- package/lib/typescript/commonjs/src/types/crash.d.ts +95 -0
- package/lib/typescript/commonjs/src/utils/action.d.ts +63 -0
- package/lib/typescript/{src → commonjs/src}/utils/api.d.ts +1 -1
- package/lib/typescript/commonjs/src/utils/crash.d.ts +12 -0
- package/lib/typescript/commonjs/src/utils/platform.d.ts +22 -0
- package/lib/typescript/{src → commonjs/src}/utils/session.d.ts +4 -0
- package/lib/typescript/module/src/NativeRumSdkReactnative.d.ts +49 -0
- package/lib/typescript/module/src/collector/action/index.d.ts +21 -0
- package/lib/typescript/module/src/collector/exception/crash.d.ts +28 -0
- package/lib/typescript/module/src/collector/exception/index.d.ts +32 -0
- package/lib/typescript/module/src/collector/index.d.ts +6 -0
- package/lib/typescript/module/src/collector/resource/api.d.ts +21 -0
- package/lib/typescript/module/src/collector/view/perf.d.ts +1 -0
- package/lib/typescript/module/src/collector/view/pv.d.ts +16 -0
- package/lib/typescript/module/src/expoConfigPlugin.d.ts +28 -0
- package/lib/typescript/module/src/index.d.ts +6 -0
- package/lib/typescript/module/src/plugin.d.ts +3 -0
- package/lib/typescript/module/src/processor/default-processor.d.ts +6 -0
- package/lib/typescript/module/src/processor/session-processor.d.ts +11 -0
- package/lib/typescript/module/src/reporter/index.d.ts +18 -0
- package/lib/typescript/module/src/reporter/monitor-reporter.d.ts +8 -0
- package/lib/typescript/module/src/shell.d.ts +57 -0
- package/lib/typescript/module/src/types/client.d.ts +27 -0
- package/lib/typescript/module/src/types/crash.d.ts +95 -0
- package/lib/typescript/module/src/types/index.d.ts +118 -0
- package/lib/typescript/module/src/utils/action.d.ts +63 -0
- package/lib/typescript/module/src/utils/api.d.ts +59 -0
- package/lib/typescript/module/src/utils/base.d.ts +8 -0
- package/lib/typescript/module/src/utils/crash.d.ts +12 -0
- package/lib/typescript/module/src/utils/platform.d.ts +22 -0
- package/lib/typescript/module/src/utils/session.d.ts +41 -0
- package/lib/typescript/module/src/utils/url.d.ts +17 -0
- package/lib/typescript/module/src/utils/view.d.ts +13 -0
- package/package.json +61 -26
- package/LICENSE +0 -20
- package/lib/module/collector/action/index.js.map +0 -1
- package/lib/module/collector/exception/index.js.map +0 -1
- package/lib/module/collector/resource/api.js.map +0 -1
- package/lib/module/collector/view/perf.js.map +0 -1
- package/lib/module/collector/view/pv.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/plugin.js.map +0 -1
- package/lib/module/processor/default-processor.js.map +0 -1
- package/lib/module/processor/session-processor.js.map +0 -1
- package/lib/module/reporter/index.js.map +0 -1
- package/lib/module/shell.js.map +0 -1
- package/lib/module/types/client.js.map +0 -1
- package/lib/module/types/index.js.map +0 -1
- package/lib/module/utils/api.js.map +0 -1
- package/lib/module/utils/base.js.map +0 -1
- package/lib/module/utils/platform.js.map +0 -1
- package/lib/module/utils/session.js.map +0 -1
- package/lib/module/utils/url.js.map +0 -1
- package/lib/module/utils/view.js.map +0 -1
- package/lib/typescript/src/collector/action/index.d.ts.map +0 -1
- package/lib/typescript/src/collector/exception/index.d.ts.map +0 -1
- package/lib/typescript/src/collector/resource/api.d.ts.map +0 -1
- package/lib/typescript/src/collector/view/perf.d.ts.map +0 -1
- package/lib/typescript/src/collector/view/pv.d.ts.map +0 -1
- package/lib/typescript/src/index.d.ts +0 -4
- package/lib/typescript/src/index.d.ts.map +0 -1
- package/lib/typescript/src/plugin.d.ts.map +0 -1
- package/lib/typescript/src/processor/default-processor.d.ts.map +0 -1
- package/lib/typescript/src/processor/session-processor.d.ts.map +0 -1
- package/lib/typescript/src/reporter/index.d.ts.map +0 -1
- package/lib/typescript/src/shell.d.ts +0 -26
- package/lib/typescript/src/shell.d.ts.map +0 -1
- package/lib/typescript/src/types/client.d.ts.map +0 -1
- package/lib/typescript/src/types/index.d.ts.map +0 -1
- package/lib/typescript/src/utils/api.d.ts.map +0 -1
- package/lib/typescript/src/utils/base.d.ts.map +0 -1
- package/lib/typescript/src/utils/platform.d.ts +0 -15
- package/lib/typescript/src/utils/platform.d.ts.map +0 -1
- package/lib/typescript/src/utils/session.d.ts.map +0 -1
- package/lib/typescript/src/utils/url.d.ts.map +0 -1
- package/lib/typescript/src/utils/view.d.ts.map +0 -1
- /package/lib/typescript/{src → commonjs/src}/collector/resource/api.d.ts +0 -0
- /package/lib/typescript/{src → commonjs/src}/collector/view/perf.d.ts +0 -0
- /package/lib/typescript/{src → commonjs/src}/plugin.d.ts +0 -0
- /package/lib/typescript/{src → commonjs/src}/processor/default-processor.d.ts +0 -0
- /package/lib/typescript/{src → commonjs/src}/types/index.d.ts +0 -0
- /package/lib/typescript/{src → commonjs/src}/utils/base.d.ts +0 -0
- /package/lib/typescript/{src → commonjs/src}/utils/url.d.ts +0 -0
- /package/lib/typescript/{src → commonjs/src}/utils/view.d.ts +0 -0
- /package/lib/typescript/{package.json → module/package.json} +0 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { isFunction } from '@arms/rum-core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 行为事件监听 & React Native 触摸自动采集
|
|
7
|
+
*
|
|
8
|
+
* 通过 patch React 的元素创建函数(createElement 及自动 JSX 运行时
|
|
9
|
+
* jsx/jsxs/jsxDEV),自动拦截 Pressable / TouchableOpacity 等可触摸组件的
|
|
10
|
+
* onPress,并从触摸事件(GestureResponderEvent)、onLayout、props/children 中
|
|
11
|
+
* 提取丰富的行为数据(位置、尺寸、持续时间、语义化名称、快照等),发射标准事件
|
|
12
|
+
* 给 ActionCollector。
|
|
13
|
+
*
|
|
14
|
+
* 说明:现代 RN/Expo(babel-preset-expo)默认使用「automatic」JSX 运行时,
|
|
15
|
+
* JSX 会被编译为 react/jsx-runtime 的 jsx()/jsxs()(生产)或
|
|
16
|
+
* react/jsx-dev-runtime 的 jsxDEV()(开发),而非 React.createElement。
|
|
17
|
+
* 因此必须同时 patch 这三者才能覆盖所有场景。
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const actionListeners = [];
|
|
21
|
+
let patched = false;
|
|
22
|
+
|
|
23
|
+
/** 语义化名称中截取的子元素文本最大长度 */
|
|
24
|
+
const MAX_TEXT_LENGTH = 20;
|
|
25
|
+
|
|
26
|
+
/** 已 patch 的目标:{ module, key, original } 用于还原 */
|
|
27
|
+
|
|
28
|
+
let patchTargets = [];
|
|
29
|
+
export function addActionListener(listener) {
|
|
30
|
+
if (isFunction(listener)) {
|
|
31
|
+
actionListeners.push(listener);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function removeActionListener(listener) {
|
|
35
|
+
const idx = actionListeners.indexOf(listener);
|
|
36
|
+
if (idx > -1) {
|
|
37
|
+
actionListeners.splice(idx, 1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 采集到的原始触摸数据,交由 emitAction 组装为标准 ActionEvent */
|
|
42
|
+
|
|
43
|
+
function emitAction(raw) {
|
|
44
|
+
const {
|
|
45
|
+
id,
|
|
46
|
+
componentName,
|
|
47
|
+
props,
|
|
48
|
+
children,
|
|
49
|
+
nativeEvent,
|
|
50
|
+
pressInTs,
|
|
51
|
+
layout
|
|
52
|
+
} = raw;
|
|
53
|
+
|
|
54
|
+
// 关键属性快照
|
|
55
|
+
const dataset = {};
|
|
56
|
+
if (props.id) dataset.id = props.id;
|
|
57
|
+
if (props.testID) dataset.testID = props.testID;
|
|
58
|
+
if (props.accessibilityLabel) {
|
|
59
|
+
dataset.accessibilityLabel = props.accessibilityLabel;
|
|
60
|
+
}
|
|
61
|
+
const text = getElementText(children);
|
|
62
|
+
const selector = componentName ? `${componentName}#${id}` : `#${id}`;
|
|
63
|
+
const label = `tap ${selector}`;
|
|
64
|
+
const name = text ? `${label}: ${text}` : label;
|
|
65
|
+
const position = getPosition(nativeEvent);
|
|
66
|
+
const duration = pressInTs && nativeEvent && typeof nativeEvent.timestamp === 'number' ? Math.max(0, Math.round(nativeEvent.timestamp - pressInTs)) : undefined;
|
|
67
|
+
const event = {
|
|
68
|
+
type: 'tap',
|
|
69
|
+
name,
|
|
70
|
+
targetName: selector,
|
|
71
|
+
duration,
|
|
72
|
+
position,
|
|
73
|
+
target: {
|
|
74
|
+
id,
|
|
75
|
+
dataset,
|
|
76
|
+
selector,
|
|
77
|
+
width: layout?.width,
|
|
78
|
+
height: layout?.height,
|
|
79
|
+
// 带 onPress 的 touchable 本身即为可交互元素
|
|
80
|
+
reaction: 1,
|
|
81
|
+
// 存在原生触摸事件视为可信交互,纯代码调用视为不可信
|
|
82
|
+
trust: nativeEvent ? 1 : 0
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
actionListeners.forEach(listener => {
|
|
86
|
+
listener(event);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 获取组件的显示名称,如 Pressable / TouchableOpacity */
|
|
91
|
+
function getComponentName(type) {
|
|
92
|
+
if (!type) return undefined;
|
|
93
|
+
if (typeof type === 'string') return type;
|
|
94
|
+
return type.displayName || type.name || undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 递归提取 children 中的文本内容,用于生成语义化名称 */
|
|
98
|
+
function getElementText(children) {
|
|
99
|
+
const acc = {
|
|
100
|
+
text: ''
|
|
101
|
+
};
|
|
102
|
+
collectText(children, acc);
|
|
103
|
+
const t = acc.text.trim();
|
|
104
|
+
if (!t) return '';
|
|
105
|
+
return t.length > MAX_TEXT_LENGTH ? t.slice(0, MAX_TEXT_LENGTH) + '...' : t;
|
|
106
|
+
}
|
|
107
|
+
function collectText(node, acc) {
|
|
108
|
+
if (node == null || typeof node === 'boolean') return;
|
|
109
|
+
if (acc.text.length > MAX_TEXT_LENGTH) return;
|
|
110
|
+
if (typeof node === 'string' || typeof node === 'number') {
|
|
111
|
+
acc.text += String(node);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(node)) {
|
|
115
|
+
node.forEach(c => collectText(c, acc));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (node.props && node.props.children != null) {
|
|
119
|
+
collectText(node.props.children, acc);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 从原生触摸事件中提取点击位置(屏幕坐标 + 元素内相对坐标) */
|
|
124
|
+
function getPosition(nativeEvent) {
|
|
125
|
+
if (!nativeEvent) return undefined;
|
|
126
|
+
const {
|
|
127
|
+
pageX,
|
|
128
|
+
pageY,
|
|
129
|
+
locationX,
|
|
130
|
+
locationY
|
|
131
|
+
} = nativeEvent;
|
|
132
|
+
if (pageX == null && pageY == null && locationX == null && locationY == null) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
x: round(pageX),
|
|
137
|
+
y: round(pageY),
|
|
138
|
+
ex: round(locationX),
|
|
139
|
+
ey: round(locationY)
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function round(n) {
|
|
143
|
+
return typeof n === 'number' ? Math.round(n) : undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 标记位:用于在已包装的 onPress 函数上做去重判断,避免重复包装
|
|
148
|
+
*/
|
|
149
|
+
const RUM_WRAPPED = '__rumWrapped';
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 生成一个「元素创建函数」的包装器。
|
|
153
|
+
*
|
|
154
|
+
* createElement / jsx / jsxs / jsxDEV 的前两个参数都是 (type, props),
|
|
155
|
+
* 其余参数(children / key / source 等)原样透传即可,因此可用统一逻辑处理。
|
|
156
|
+
*
|
|
157
|
+
* 除包装 onPress 采集点击外,还会顺带包装 onPressIn(记录按下时间以计算
|
|
158
|
+
* duration)与 onLayout(记录元素尺寸 width/height),以补全行为数据。
|
|
159
|
+
*
|
|
160
|
+
* childrenInRest:经典 createElement 的 children 以可变参数传入(在 rest 中),
|
|
161
|
+
* 而 automatic 运行时 jsx/jsxs/jsxDEV 的 children 在 props.children 中。
|
|
162
|
+
*/
|
|
163
|
+
function makeWrapper(original, touchableTypes, childrenInRest) {
|
|
164
|
+
return function patchedElementFactory(type, props, ...rest) {
|
|
165
|
+
if (props && touchableTypes.has(type) && isFunction(props.onPress)) {
|
|
166
|
+
const id = props.id || props.testID || props.accessibilityLabel;
|
|
167
|
+
if (id && !props.onPress[RUM_WRAPPED]) {
|
|
168
|
+
const componentName = getComponentName(type);
|
|
169
|
+
const children = childrenInRest ? rest.length <= 1 ? rest[0] : rest : props.children;
|
|
170
|
+
const originalOnPress = props.onPress;
|
|
171
|
+
const originalOnPressIn = props.onPressIn;
|
|
172
|
+
const originalOnLayout = props.onLayout;
|
|
173
|
+
|
|
174
|
+
// 闭包内保存按下时间与布局尺寸,供 onPress 采集时读取
|
|
175
|
+
let pressInTs = 0;
|
|
176
|
+
let layout;
|
|
177
|
+
const wrappedOnPressIn = (...args) => {
|
|
178
|
+
const ne = args[0] && args[0].nativeEvent;
|
|
179
|
+
pressInTs = ne && typeof ne.timestamp === 'number' ? ne.timestamp : 0;
|
|
180
|
+
if (isFunction(originalOnPressIn)) return originalOnPressIn(...args);
|
|
181
|
+
};
|
|
182
|
+
const wrappedOnLayout = (...args) => {
|
|
183
|
+
const ne = args[0] && args[0].nativeEvent;
|
|
184
|
+
const l = ne && ne.layout;
|
|
185
|
+
if (l) {
|
|
186
|
+
layout = {
|
|
187
|
+
width: round(l.width),
|
|
188
|
+
height: round(l.height)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (isFunction(originalOnLayout)) return originalOnLayout(...args);
|
|
192
|
+
};
|
|
193
|
+
const wrappedOnPress = (...args) => {
|
|
194
|
+
emitAction({
|
|
195
|
+
id,
|
|
196
|
+
componentName,
|
|
197
|
+
props,
|
|
198
|
+
children,
|
|
199
|
+
nativeEvent: args[0] && args[0].nativeEvent,
|
|
200
|
+
pressInTs,
|
|
201
|
+
layout
|
|
202
|
+
});
|
|
203
|
+
return originalOnPress(...args);
|
|
204
|
+
};
|
|
205
|
+
wrappedOnPress[RUM_WRAPPED] = true;
|
|
206
|
+
return original(type, {
|
|
207
|
+
...props,
|
|
208
|
+
onPress: wrappedOnPress,
|
|
209
|
+
onPressIn: wrappedOnPressIn,
|
|
210
|
+
onLayout: wrappedOnLayout
|
|
211
|
+
}, ...rest);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return original(type, props, ...rest);
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 安全地按字面量 require 一个模块,失败返回 undefined。
|
|
220
|
+
*
|
|
221
|
+
* 注意:Metro 的 babel 插件禁止动态 `require(变量)`,因此这里必须用
|
|
222
|
+
* 字面量字符串分别 require,不能抽成 `safeRequire(name)` 之类的通用函数。
|
|
223
|
+
*/
|
|
224
|
+
function safeRequireJsxRuntime() {
|
|
225
|
+
try {
|
|
226
|
+
return require('react/jsx-runtime');
|
|
227
|
+
} catch {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function safeRequireJsxDevRuntime() {
|
|
232
|
+
try {
|
|
233
|
+
return require('react/jsx-dev-runtime');
|
|
234
|
+
} catch {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function patchModuleFn(mod, key, touchableTypes) {
|
|
239
|
+
if (!mod || !isFunction(mod[key])) return;
|
|
240
|
+
const original = mod[key];
|
|
241
|
+
// 经典 createElement 的 children 以可变参数传入;jsx 运行时则在 props.children
|
|
242
|
+
const childrenInRest = key === 'createElement';
|
|
243
|
+
try {
|
|
244
|
+
mod[key] = makeWrapper(original, touchableTypes, childrenInRest);
|
|
245
|
+
patchTargets.push({
|
|
246
|
+
mod,
|
|
247
|
+
key,
|
|
248
|
+
original
|
|
249
|
+
});
|
|
250
|
+
} catch {
|
|
251
|
+
// 某些环境下属性只读,忽略
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 开始自动采集用户点击行为
|
|
257
|
+
*
|
|
258
|
+
* 原理:patch React.createElement 以及 automatic JSX 运行时的 jsx/jsxs/jsxDEV,
|
|
259
|
+
* 当组件为可触摸类型且含 onPress + id/testID/accessibilityLabel 时,
|
|
260
|
+
* 包装 onPress 使其在调用时自动 emit action 事件。
|
|
261
|
+
*/
|
|
262
|
+
export function startTrackingActions() {
|
|
263
|
+
if (patched) return;
|
|
264
|
+
const React = require('react');
|
|
265
|
+
const RN = require('react-native');
|
|
266
|
+
const touchableTypes = new Set([RN.Pressable, RN.TouchableOpacity, RN.TouchableHighlight, RN.TouchableWithoutFeedback, RN.TouchableNativeFeedback, RN.Button].filter(Boolean));
|
|
267
|
+
|
|
268
|
+
// 经典运行时
|
|
269
|
+
patchModuleFn(React, 'createElement', touchableTypes);
|
|
270
|
+
// automatic 运行时(生产)
|
|
271
|
+
const jsxRuntime = safeRequireJsxRuntime();
|
|
272
|
+
patchModuleFn(jsxRuntime, 'jsx', touchableTypes);
|
|
273
|
+
patchModuleFn(jsxRuntime, 'jsxs', touchableTypes);
|
|
274
|
+
// automatic 运行时(开发)
|
|
275
|
+
const jsxDevRuntime = safeRequireJsxDevRuntime();
|
|
276
|
+
patchModuleFn(jsxDevRuntime, 'jsxDEV', touchableTypes);
|
|
277
|
+
patched = patchTargets.length > 0;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 停止自动采集
|
|
282
|
+
*/
|
|
283
|
+
export function stopTrackingActions() {
|
|
284
|
+
if (!patched) return;
|
|
285
|
+
patchTargets.forEach(({
|
|
286
|
+
mod,
|
|
287
|
+
key,
|
|
288
|
+
original
|
|
289
|
+
}) => {
|
|
290
|
+
try {
|
|
291
|
+
mod[key] = original;
|
|
292
|
+
} catch {
|
|
293
|
+
// 忽略还原失败
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
patchTargets = [];
|
|
297
|
+
patched = false;
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=action.js.map
|
package/lib/module/utils/api.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { isFunction, isNumber, isObject, isString, ResourceStatus } from
|
|
3
|
+
import { isFunction, isNumber, isObject, isString, ResourceStatus } from '@arms/rum-core';
|
|
4
4
|
export const ApiType = {
|
|
5
5
|
FETCH: 'fetch',
|
|
6
6
|
XHR: 'xmlhttprequest'
|
|
@@ -10,25 +10,25 @@ export function reviseApiAttr(attrs) {
|
|
|
10
10
|
if (!attrs || !isObject(attrs)) return;
|
|
11
11
|
const obj = {};
|
|
12
12
|
if (isString(attrs.name)) {
|
|
13
|
-
obj
|
|
13
|
+
obj.name = attrs.name.substring(0, 1e3);
|
|
14
14
|
}
|
|
15
15
|
if (isString(attrs.message)) {
|
|
16
|
-
obj
|
|
16
|
+
obj.message = attrs.message.substring(0, 1e3);
|
|
17
17
|
}
|
|
18
18
|
if ('success' in attrs) {
|
|
19
|
-
obj
|
|
19
|
+
obj.success = attrs.success ? 1 : 0;
|
|
20
20
|
}
|
|
21
21
|
if (isNumber(attrs.duration) && attrs.duration >= 0) {
|
|
22
|
-
obj
|
|
22
|
+
obj.duration = attrs.duration;
|
|
23
23
|
}
|
|
24
24
|
if (isString(attrs.status_code)) {
|
|
25
|
-
obj
|
|
25
|
+
obj.status_code = attrs.status_code.substring(0, 100);
|
|
26
26
|
}
|
|
27
27
|
if (isNumber(attrs.status_code)) {
|
|
28
|
-
obj
|
|
28
|
+
obj.status_code = attrs.status_code;
|
|
29
29
|
}
|
|
30
30
|
if (isString(attrs.snapshots)) {
|
|
31
|
-
obj
|
|
31
|
+
obj.snapshots = attrs.snapshots.substring(0, 5e3);
|
|
32
32
|
}
|
|
33
33
|
return obj;
|
|
34
34
|
}
|
|
@@ -65,8 +65,8 @@ export function getResourceSuccessByCode(status_code) {
|
|
|
65
65
|
let status;
|
|
66
66
|
const THRESHOLD = [200, 300];
|
|
67
67
|
if (status_code) {
|
|
68
|
-
|
|
69
|
-
status =
|
|
68
|
+
const code = Number(status_code);
|
|
69
|
+
status = code >= THRESHOLD[0] && code < THRESHOLD[1] ? ResourceStatus.Success : ResourceStatus.Failed;
|
|
70
70
|
} else {
|
|
71
71
|
status = ResourceStatus.Unknown;
|
|
72
72
|
}
|
package/lib/module/utils/base.js
CHANGED
|
@@ -39,10 +39,10 @@ export function getStringSize(str, encode = 'utf8') {
|
|
|
39
39
|
const code = str.charCodeAt(i);
|
|
40
40
|
|
|
41
41
|
// 检查代理对(有效的高位代理后跟低位代理)
|
|
42
|
-
if (code >=
|
|
42
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
43
43
|
if (i + 1 < str.length) {
|
|
44
44
|
const nextCode = str.charCodeAt(i + 1);
|
|
45
|
-
if (nextCode >=
|
|
45
|
+
if (nextCode >= 0xdc00 && nextCode <= 0xdfff) {
|
|
46
46
|
bytes += 4; // 有效的代理对(4字节)
|
|
47
47
|
i++; // 跳过低位代理
|
|
48
48
|
continue;
|
|
@@ -59,25 +59,25 @@ export function getStringSize(str, encode = 'utf8') {
|
|
|
59
59
|
let code = str.charCodeAt(i);
|
|
60
60
|
|
|
61
61
|
// 处理代理对(UTF-16辅助平面)
|
|
62
|
-
if (code >=
|
|
62
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
63
63
|
if (i + 1 < str.length) {
|
|
64
64
|
const nextCode = str.charCodeAt(i + 1);
|
|
65
|
-
if (nextCode >=
|
|
65
|
+
if (nextCode >= 0xdc00 && nextCode <= 0xdfff) {
|
|
66
66
|
// 计算实际的Unicode码点
|
|
67
|
-
code = (code -
|
|
67
|
+
code = (code - 0xd800) * 0x400 + (nextCode - 0xdc00) + 0x10000;
|
|
68
68
|
i++; // 跳过低位代理
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// 根据码点值计算UTF-8字节长度
|
|
74
|
-
if (code <=
|
|
74
|
+
if (code <= 0x7f) {
|
|
75
75
|
totalBytes += 1; // ASCII字符(1字节)
|
|
76
|
-
} else if (code <=
|
|
76
|
+
} else if (code <= 0x7ff) {
|
|
77
77
|
totalBytes += 2; // 拉丁文补充等(2字节)
|
|
78
|
-
} else if (code <=
|
|
78
|
+
} else if (code <= 0xffff) {
|
|
79
79
|
totalBytes += 3; // 基本多语言平面(3字节)
|
|
80
|
-
} else if (code <=
|
|
80
|
+
} else if (code <= 0x10ffff) {
|
|
81
81
|
totalBytes += 4; // 辅助平面(4字节)
|
|
82
82
|
} else {
|
|
83
83
|
totalBytes += 3; // 无效码点,使用替换字符长度
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { RumEventType, logger } from '@arms/rum-core';
|
|
4
|
+
/**
|
|
5
|
+
* 将原生崩溃数据转换为 RumEvent 异常事件,对齐存储模型 Exception 表的
|
|
6
|
+
* Attributes(session/view/exception 基础信息)+ Metadata(堆栈、快照等)字段。
|
|
7
|
+
*/
|
|
8
|
+
export function convertCrashToRumEvent(crashData) {
|
|
9
|
+
const exceptionName = crashData.signal?.name || crashData.exception?.class_name || 'NativeCrash';
|
|
10
|
+
const exceptionMessage = buildMessage(crashData);
|
|
11
|
+
// NDK 崩溃的 frames 在顶层,Java 崩溃的 frames 在 crashed_thread 内
|
|
12
|
+
const frames = crashData.frames || crashData.crashed_thread?.frames || [];
|
|
13
|
+
const stack = formatStack(frames);
|
|
14
|
+
// 兼容旧版字符串格式:frames[0] 可能是字符串
|
|
15
|
+
const firstFrame = frames[0];
|
|
16
|
+
const file = typeof firstFrame === 'object' ? firstFrame?.library || '' : '';
|
|
17
|
+
|
|
18
|
+
// 从第一帧提取 line,原生崩溃无列号概念,column 固定为 0
|
|
19
|
+
let line = 0;
|
|
20
|
+
let column = 0;
|
|
21
|
+
if (frames.length > 0) {
|
|
22
|
+
const topFrame = frames[0];
|
|
23
|
+
if (topFrame && typeof topFrame === 'object' && typeof topFrame.line === 'number') {
|
|
24
|
+
line = topFrame.line;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const view = {
|
|
28
|
+
id: crashData.context?.view_id || '',
|
|
29
|
+
name: crashData.context?.view_name || ''
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// 防御性转换:NDK 旧版本使用秒级 timestamp,新版本已改为毫秒
|
|
33
|
+
// 如果 timestamp 小于 1e12(约 2001 年的毫秒值),说明是秒级,需乘以 1000
|
|
34
|
+
let timestamp = crashData.timestamp || Date.now();
|
|
35
|
+
if (timestamp < 1e12) {
|
|
36
|
+
timestamp = timestamp * 1000;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// binary_images uuid 字段兼容:统一为 uuid,保留原 build_id/debug_id
|
|
40
|
+
const rawImages = crashData.binary_images || [];
|
|
41
|
+
const normalizedImages = rawImages.map(img => {
|
|
42
|
+
if (!img.uuid && (img.build_id || img.debug_id)) {
|
|
43
|
+
return {
|
|
44
|
+
...img,
|
|
45
|
+
uuid: img.build_id || img.debug_id
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return img;
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
event_type: RumEventType.EXCEPTION,
|
|
52
|
+
timestamp,
|
|
53
|
+
// 设置崩溃时的原始 session_id,新版 rum-core dispatchEvents 会按
|
|
54
|
+
// session_id 分组上报,不再丢弃非当前 session 的事件
|
|
55
|
+
session_id: crashData.context?.session_id,
|
|
56
|
+
view,
|
|
57
|
+
times: 1,
|
|
58
|
+
// === Attributes ===
|
|
59
|
+
source: 'event',
|
|
60
|
+
type: 'crash',
|
|
61
|
+
name: exceptionName,
|
|
62
|
+
message: exceptionMessage,
|
|
63
|
+
file,
|
|
64
|
+
line,
|
|
65
|
+
column,
|
|
66
|
+
// === Metadata ===
|
|
67
|
+
stack,
|
|
68
|
+
caused_by: crashData.exception?.message || (crashData.signal ? `${crashData.signal.name || ''} at ${crashData.signal.fault_address || ''}` : ''),
|
|
69
|
+
binary_images: JSON.stringify(normalizedImages),
|
|
70
|
+
snapshots: JSON.stringify({
|
|
71
|
+
device: crashData.device || {},
|
|
72
|
+
registers: crashData.registers || {},
|
|
73
|
+
app: crashData.app || {},
|
|
74
|
+
crash_context: crashData.context || {},
|
|
75
|
+
// NDK 特有字段透传:arch 决定后端符号化的寄存器解释方式和地址宽度;
|
|
76
|
+
// pid 用于关联系统日志;signal.number 为 NDK 写入的原始信号编号
|
|
77
|
+
arch: crashData.arch || '',
|
|
78
|
+
pid: crashData.pid,
|
|
79
|
+
signal_number: crashData.signal?.number,
|
|
80
|
+
// 崩溃线程 ID:NDK 崩溃使用顶层 tid,Java 崩溃使用 crashed_thread.id
|
|
81
|
+
thread_id: String(crashData.crashed_thread?.id ?? crashData.tid ?? ''),
|
|
82
|
+
// 崩溃机制信息放入 snapshots 容器
|
|
83
|
+
...(crashData.mechanism ? {
|
|
84
|
+
mechanism: crashData.mechanism
|
|
85
|
+
} : {})
|
|
86
|
+
})
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function buildMessage(crashData) {
|
|
90
|
+
if (crashData.exception?.message) {
|
|
91
|
+
return crashData.exception.message;
|
|
92
|
+
}
|
|
93
|
+
const sig = crashData.signal;
|
|
94
|
+
if (sig) {
|
|
95
|
+
const addr = sig.fault_address || '';
|
|
96
|
+
return `${sig.name || ''} (${sig.code ?? ''})${addr ? ' at ' + addr : ''}`.trim();
|
|
97
|
+
}
|
|
98
|
+
return 'Native crash';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 格式化堆栈为可读字符串。
|
|
103
|
+
* 兼容两种 frames 格式:
|
|
104
|
+
* - 新版(对象数组):[{address, library, symbol, offset}, ...]
|
|
105
|
+
* - 旧版(字符串数组):["0x1234", ...](旧版 NDK 崩溃文件)
|
|
106
|
+
*/
|
|
107
|
+
function formatStack(frames) {
|
|
108
|
+
if (!frames || frames.length === 0) return '';
|
|
109
|
+
return frames.map((frame, index) => {
|
|
110
|
+
// 兼容旧版 NDK 的纯字符串格式
|
|
111
|
+
if (typeof frame === 'string') {
|
|
112
|
+
return `#${index} <unknown> ${frame} ()`;
|
|
113
|
+
}
|
|
114
|
+
// 截断标记帧:双侧截断时插入的中间标记
|
|
115
|
+
if (frame.truncated) {
|
|
116
|
+
const count = frame.omitted_count ?? -1;
|
|
117
|
+
return `#${index} ... ${count} frames omitted ...`;
|
|
118
|
+
}
|
|
119
|
+
const library = frame.library || '<unknown>';
|
|
120
|
+
const symbol = frame.symbol || '';
|
|
121
|
+
const address = frame.address || '';
|
|
122
|
+
const offset = frame.offset || '';
|
|
123
|
+
if (symbol) {
|
|
124
|
+
return `#${index} ${library} ${symbol} + ${offset}`;
|
|
125
|
+
}
|
|
126
|
+
return `#${index} ${library} ${address} (${offset})`;
|
|
127
|
+
}).join('\n');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 安全调用原生桥方法,异常时静默降级。
|
|
132
|
+
*/
|
|
133
|
+
export async function safeNativeCall(fn, label) {
|
|
134
|
+
try {
|
|
135
|
+
return await fn();
|
|
136
|
+
} catch (e) {
|
|
137
|
+
logger.warn('native-bridge', `${label || 'native call'} failed`, e);
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=crash.js.map
|