@arms/rum-reactnative 0.1.3-beta.5 → 0.1.4-beta.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/README.md +9 -2
- package/android/build.gradle +12 -1
- package/android/gradle.properties +3 -1
- package/android/src/main/AndroidManifest.xml +4 -1
- package/android/src/main/java/com/rumsdkreactnative/RumSdkReactnativeModule.kt +67 -14
- package/android/src/main/java/com/rumsdkreactnative/RumSdkReactnativePackage.kt +9 -2
- package/docs/ReactNative SDK/351/205/215/347/275/256/345/217/202/350/200/203.md" +25 -0
- package/docs/SDK /347/211/210/346/234/254/350/257/264/346/230/216.md" +20 -0
- package/docs//346/216/245/345/205/245ReactNative/345/272/224/347/224/250.md +2 -1
- package/ios/RumAppStateRecorder.h +69 -0
- package/ios/RumAppStateRecorder.mm +547 -0
- package/ios/RumCrashReporter.h +32 -0
- package/ios/RumCrashReporter.mm +97 -11
- package/ios/RumSdkReactnative.h +11 -1
- package/ios/RumSdkReactnative.mm +115 -29
- package/lib/commonjs/NativeRumSdkReactnative.js +1 -54
- package/lib/commonjs/collector/action/index.js +1 -108
- package/lib/commonjs/collector/exception/crash.js +1 -148
- package/lib/commonjs/collector/exception/index.js +1 -238
- package/lib/commonjs/collector/exception/oom.js +1 -0
- package/lib/commonjs/collector/index.js +1 -42
- package/lib/commonjs/collector/resource/api.js +1 -292
- package/lib/commonjs/collector/view/perf.js +1 -79
- package/lib/commonjs/collector/view/pv.js +1 -88
- package/lib/commonjs/expoConfigPlugin.js +1 -37
- package/lib/commonjs/index.js +1 -29
- package/lib/commonjs/plugin.js +1 -47
- package/lib/commonjs/processor/default-processor.js +1 -27
- package/lib/commonjs/processor/session-processor.js +1 -47
- package/lib/commonjs/reporter/index.js +1 -148
- package/lib/commonjs/reporter/monitor-reporter.js +1 -43
- package/lib/commonjs/shell.js +1 -91
- package/lib/commonjs/types/client.js +1 -6
- package/lib/commonjs/types/crash.js +1 -37
- package/lib/commonjs/types/index.js +1 -2
- package/lib/commonjs/types/oom.js +1 -0
- package/lib/commonjs/utils/action.js +1 -305
- package/lib/commonjs/utils/api.js +1 -84
- package/lib/commonjs/utils/base.js +1 -95
- package/lib/commonjs/utils/crash.js +1 -149
- package/lib/commonjs/utils/platform.js +1 -162
- package/lib/commonjs/utils/session.js +1 -158
- package/lib/commonjs/utils/url.js +1 -59
- package/lib/commonjs/utils/view.js +1 -93
- package/lib/module/NativeRumSdkReactnative.js +1 -51
- package/lib/module/collector/action/index.js +1 -103
- package/lib/module/collector/exception/crash.js +1 -143
- package/lib/module/collector/exception/index.js +1 -233
- package/lib/module/collector/exception/oom.js +1 -0
- package/lib/module/collector/index.js +1 -8
- package/lib/module/collector/resource/api.js +1 -287
- package/lib/module/collector/view/perf.js +1 -79
- package/lib/module/collector/view/pv.js +1 -83
- package/lib/module/expoConfigPlugin.js +1 -33
- package/lib/module/index.js +1 -7
- package/lib/module/plugin.js +1 -42
- package/lib/module/processor/default-processor.js +1 -22
- package/lib/module/processor/session-processor.js +1 -42
- package/lib/module/reporter/index.js +1 -144
- package/lib/module/reporter/monitor-reporter.js +1 -38
- package/lib/module/shell.js +1 -84
- package/lib/module/types/client.js +1 -4
- package/lib/module/types/crash.js +1 -33
- package/lib/module/types/index.js +1 -2
- package/lib/module/types/oom.js +1 -0
- package/lib/module/utils/action.js +1 -299
- package/lib/module/utils/api.js +1 -76
- package/lib/module/utils/base.js +1 -90
- package/lib/module/utils/crash.js +1 -144
- package/lib/module/utils/platform.js +1 -150
- package/lib/module/utils/session.js +1 -154
- package/lib/module/utils/url.js +1 -54
- package/lib/module/utils/view.js +1 -88
- package/lib/typescript/commonjs/src/NativeRumSdkReactnative.d.ts +10 -0
- package/lib/typescript/commonjs/src/collector/exception/oom.d.ts +34 -0
- package/lib/typescript/commonjs/src/collector/index.d.ts +1 -0
- package/lib/typescript/commonjs/src/shell.d.ts +1 -1
- package/lib/typescript/commonjs/src/types/client.d.ts +27 -0
- package/lib/typescript/commonjs/src/types/oom.d.ts +58 -0
- package/lib/typescript/commonjs/src/utils/crash.d.ts +13 -0
- package/lib/typescript/module/src/NativeRumSdkReactnative.d.ts +10 -0
- package/lib/typescript/module/src/collector/exception/oom.d.ts +34 -0
- package/lib/typescript/module/src/collector/index.d.ts +1 -0
- package/lib/typescript/module/src/shell.d.ts +1 -1
- package/lib/typescript/module/src/types/client.d.ts +27 -0
- package/lib/typescript/module/src/types/oom.d.ts +58 -0
- package/lib/typescript/module/src/utils/crash.d.ts +13 -0
- package/package.json +1 -2
- package/src/NativeRumSdkReactnative.ts +18 -0
|
@@ -1,299 +1 @@
|
|
|
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
|
|
1
|
+
"use strict";import{isFunction}from"@arms/rum-core";const actionListeners=[];let patched=!1;const MAX_TEXT_LENGTH=20;let patchTargets=[];export function addActionListener(t){isFunction(t)&&actionListeners.push(t)}export function removeActionListener(t){const e=actionListeners.indexOf(t);e>-1&&actionListeners.splice(e,1)}function emitAction(t){const{id:e,componentName:n,props:i,children:o,nativeEvent:r,pressInTs:c,layout:s}=t,a={};i.id&&(a.id=i.id),i.testID&&(a.testID=i.testID),i.accessibilityLabel&&(a.accessibilityLabel=i.accessibilityLabel);const u=getElementText(o),l=n?`${n}#${e}`:`#${e}`,p=`tap ${l}`,h=u?`${p}: ${u}`:p,d=getPosition(r),m={type:"tap",name:h,targetName:l,duration:c&&r&&"number"==typeof r.timestamp?Math.max(0,Math.round(r.timestamp-c)):void 0,position:d,target:{id:e,dataset:a,selector:l,width:s?.width,height:s?.height,reaction:1,trust:r?1:0}};actionListeners.forEach(t=>{t(m)})}function getComponentName(t){if(t)return"string"==typeof t?t:t.displayName||t.name||void 0}function getElementText(t){const e={text:""};collectText(t,e);const n=e.text.trim();return n?n.length>20?n.slice(0,20)+"...":n:""}function collectText(t,e){null!=t&&"boolean"!=typeof t&&(e.text.length>20||("string"!=typeof t&&"number"!=typeof t?Array.isArray(t)?t.forEach(t=>collectText(t,e)):t.props&&null!=t.props.children&&collectText(t.props.children,e):e.text+=String(t)))}function getPosition(t){if(!t)return;const{pageX:e,pageY:n,locationX:i,locationY:o}=t;return null!=e||null!=n||null!=i||null!=o?{x:round(e),y:round(n),ex:round(i),ey:round(o)}:void 0}function round(t){return"number"==typeof t?Math.round(t):void 0}const RUM_WRAPPED="__rumWrapped";function makeWrapper(t,e,n){return function(i,o,...r){if(o&&e.has(i)&&isFunction(o.onPress)){const e=o.id||o.testID||o.accessibilityLabel;if(e&&!o.onPress[RUM_WRAPPED]){const c=getComponentName(i),s=n?r.length<=1?r[0]:r:o.children,a=o.onPress,u=o.onPressIn,l=o.onLayout;let p,h=0;const d=(...t)=>{const e=t[0]&&t[0].nativeEvent;if(h=e&&"number"==typeof e.timestamp?e.timestamp:0,isFunction(u))return u(...t)},m=(...t)=>{const e=t[0]&&t[0].nativeEvent,n=e&&e.layout;if(n&&(p={width:round(n.width),height:round(n.height)}),isFunction(l))return l(...t)},f=(...t)=>(emitAction({id:e,componentName:c,props:o,children:s,nativeEvent:t[0]&&t[0].nativeEvent,pressInTs:h,layout:p}),a(...t));return f[RUM_WRAPPED]=!0,t(i,{...o,onPress:f,onPressIn:d,onLayout:m},...r)}}return t(i,o,...r)}}function safeRequireJsxRuntime(){try{return require("react/jsx-runtime")}catch{return}}function safeRequireJsxDevRuntime(){try{return require("react/jsx-dev-runtime")}catch{return}}function patchModuleFn(t,e,n){if(!t||!isFunction(t[e]))return;const i=t[e],o="createElement"===e;try{t[e]=makeWrapper(i,n,o),patchTargets.push({mod:t,key:e,original:i})}catch{}}export function startTrackingActions(){if(patched)return;const t=require("react"),e=require("react-native"),n=new Set([e.Pressable,e.TouchableOpacity,e.TouchableHighlight,e.TouchableWithoutFeedback,e.TouchableNativeFeedback,e.Button].filter(Boolean));patchModuleFn(t,"createElement",n);const i=safeRequireJsxRuntime();patchModuleFn(i,"jsx",n),patchModuleFn(i,"jsxs",n);patchModuleFn(safeRequireJsxDevRuntime(),"jsxDEV",n),patched=patchTargets.length>0}export function stopTrackingActions(){patched&&(patchTargets.forEach(({mod:t,key:e,original:n})=>{try{t[e]=n}catch{}}),patchTargets=[],patched=!1)}
|
package/lib/module/utils/api.js
CHANGED
|
@@ -1,76 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { isFunction, isNumber, isObject, isString, ResourceStatus } from '@arms/rum-core';
|
|
4
|
-
export const ApiType = {
|
|
5
|
-
FETCH: 'fetch',
|
|
6
|
-
XHR: 'xmlhttprequest'
|
|
7
|
-
// NativeXHR: 'fetch',
|
|
8
|
-
};
|
|
9
|
-
export function reviseApiAttr(attrs) {
|
|
10
|
-
if (!attrs || !isObject(attrs)) return;
|
|
11
|
-
const obj = {};
|
|
12
|
-
if (isString(attrs.name)) {
|
|
13
|
-
obj.name = attrs.name.substring(0, 1e3);
|
|
14
|
-
}
|
|
15
|
-
if (isString(attrs.message)) {
|
|
16
|
-
obj.message = attrs.message.substring(0, 1e3);
|
|
17
|
-
}
|
|
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;
|
|
21
|
-
}
|
|
22
|
-
if (isNumber(attrs.duration) && attrs.duration >= 0) {
|
|
23
|
-
obj.duration = attrs.duration;
|
|
24
|
-
}
|
|
25
|
-
if (isString(attrs.status_code)) {
|
|
26
|
-
obj.status_code = attrs.status_code.substring(0, 100);
|
|
27
|
-
}
|
|
28
|
-
if (isNumber(attrs.status_code)) {
|
|
29
|
-
obj.status_code = attrs.status_code;
|
|
30
|
-
}
|
|
31
|
-
if (isString(attrs.snapshots)) {
|
|
32
|
-
obj.snapshots = attrs.snapshots.substring(0, 5e3);
|
|
33
|
-
}
|
|
34
|
-
return obj;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 根据指定 URL 获取视图的名称,默认为其 path 部分
|
|
39
|
-
* @param url 需要提取路径部分的 URL
|
|
40
|
-
* @param spaMode 页面 SPA 路由模式
|
|
41
|
-
*/
|
|
42
|
-
export function getPathByURL(url) {
|
|
43
|
-
try {
|
|
44
|
-
const urlObj = new URL(url);
|
|
45
|
-
return urlObj.pathname || '/';
|
|
46
|
-
} catch (error) {
|
|
47
|
-
return url;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
export function getResourceName(url, parseFunction) {
|
|
51
|
-
let name;
|
|
52
|
-
if (parseFunction && isFunction(parseFunction)) {
|
|
53
|
-
name = parseFunction(url);
|
|
54
|
-
} else {
|
|
55
|
-
name = getPathByURL(url);
|
|
56
|
-
}
|
|
57
|
-
return name;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 判断成功还是失败
|
|
62
|
-
* @param status_code
|
|
63
|
-
* @returns
|
|
64
|
-
*/
|
|
65
|
-
export function getResourceSuccessByCode(status_code) {
|
|
66
|
-
let status;
|
|
67
|
-
const THRESHOLD = [200, 300];
|
|
68
|
-
if (status_code) {
|
|
69
|
-
const code = Number(status_code);
|
|
70
|
-
status = code >= THRESHOLD[0] && code < THRESHOLD[1] ? ResourceStatus.Success : ResourceStatus.Failed;
|
|
71
|
-
} else {
|
|
72
|
-
status = ResourceStatus.Unknown;
|
|
73
|
-
}
|
|
74
|
-
return status;
|
|
75
|
-
}
|
|
76
|
-
//# sourceMappingURL=api.js.map
|
|
1
|
+
"use strict";import{isFunction,isNumber,isObject,isString,ResourceStatus}from"@arms/rum-core";export const ApiType={FETCH:"fetch",XHR:"xmlhttprequest"};export function reviseApiAttr(s){if(!s||!isObject(s))return;const t={};return isString(s.name)&&(t.name=s.name.substring(0,1e3)),isString(s.message)&&(t.message=s.message.substring(0,1e3)),isNumber(s.success)&&s.success>=-1&&s.success<=1&&(t.success=s.success),isNumber(s.duration)&&s.duration>=0&&(t.duration=s.duration),isString(s.status_code)&&(t.status_code=s.status_code.substring(0,100)),isNumber(s.status_code)&&(t.status_code=s.status_code),isString(s.snapshots)&&(t.snapshots=s.snapshots.substring(0,5e3)),t}export function getPathByURL(s){try{return new URL(s).pathname||"/"}catch(t){return s}}export function getResourceName(s,t){let e;return e=t&&isFunction(t)?t(s):getPathByURL(s),e}export function getResourceSuccessByCode(s){let t;const e=[200,300];if(s){const r=Number(s);t=r>=e[0]&&r<e[1]?ResourceStatus.Success:ResourceStatus.Failed}else t=ResourceStatus.Unknown;return t}
|
package/lib/module/utils/base.js
CHANGED
|
@@ -1,90 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
// /**
|
|
4
|
-
// * 保留指定位数的小数
|
|
5
|
-
// * @param num 原数据
|
|
6
|
-
// * @param decimal 小数位数
|
|
7
|
-
// * @returns
|
|
8
|
-
// */
|
|
9
|
-
// export function formatNumber(num: number, decimal: number = 3): number {
|
|
10
|
-
// if (!num) {
|
|
11
|
-
// return num;
|
|
12
|
-
// }
|
|
13
|
-
// let str = num.toString();
|
|
14
|
-
// const index = str.indexOf('.');
|
|
15
|
-
// if (index !== -1) {
|
|
16
|
-
// str = str.substring(0, decimal + index + 1);
|
|
17
|
-
// } else {
|
|
18
|
-
// str = str.substring(0);
|
|
19
|
-
// }
|
|
20
|
-
// return parseFloat(str);
|
|
21
|
-
// }
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 获取获取当前时间 fix Data.now() 存在的缺陷
|
|
25
|
-
* duration方法用于计算时间跨度,由于Date.now存在bug,可能为负数,尽可能使用performance.now计算
|
|
26
|
-
* @returns
|
|
27
|
-
*/
|
|
28
|
-
export function getCurrentTime() {
|
|
29
|
-
return new Date().getTime();
|
|
30
|
-
}
|
|
31
|
-
export function getStringSize(str, encode = 'utf8') {
|
|
32
|
-
if (typeof str !== 'string') {
|
|
33
|
-
throw new TypeError('Input must be a string');
|
|
34
|
-
}
|
|
35
|
-
const encoding = encode.toLowerCase();
|
|
36
|
-
if (encoding === 'utf16') {
|
|
37
|
-
let bytes = 0;
|
|
38
|
-
for (let i = 0; i < str.length; i++) {
|
|
39
|
-
const code = str.charCodeAt(i);
|
|
40
|
-
|
|
41
|
-
// 检查代理对(有效的高位代理后跟低位代理)
|
|
42
|
-
if (code >= 0xd800 && code <= 0xdbff) {
|
|
43
|
-
if (i + 1 < str.length) {
|
|
44
|
-
const nextCode = str.charCodeAt(i + 1);
|
|
45
|
-
if (nextCode >= 0xdc00 && nextCode <= 0xdfff) {
|
|
46
|
-
bytes += 4; // 有效的代理对(4字节)
|
|
47
|
-
i++; // 跳过低位代理
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
bytes += 2; // 基本字符或无效代理(2字节)
|
|
53
|
-
}
|
|
54
|
-
return bytes;
|
|
55
|
-
}
|
|
56
|
-
if (encoding === 'utf8') {
|
|
57
|
-
let totalBytes = 0;
|
|
58
|
-
for (let i = 0; i < str.length; i++) {
|
|
59
|
-
let code = str.charCodeAt(i);
|
|
60
|
-
|
|
61
|
-
// 处理代理对(UTF-16辅助平面)
|
|
62
|
-
if (code >= 0xd800 && code <= 0xdbff) {
|
|
63
|
-
if (i + 1 < str.length) {
|
|
64
|
-
const nextCode = str.charCodeAt(i + 1);
|
|
65
|
-
if (nextCode >= 0xdc00 && nextCode <= 0xdfff) {
|
|
66
|
-
// 计算实际的Unicode码点
|
|
67
|
-
code = (code - 0xd800) * 0x400 + (nextCode - 0xdc00) + 0x10000;
|
|
68
|
-
i++; // 跳过低位代理
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// 根据码点值计算UTF-8字节长度
|
|
74
|
-
if (code <= 0x7f) {
|
|
75
|
-
totalBytes += 1; // ASCII字符(1字节)
|
|
76
|
-
} else if (code <= 0x7ff) {
|
|
77
|
-
totalBytes += 2; // 拉丁文补充等(2字节)
|
|
78
|
-
} else if (code <= 0xffff) {
|
|
79
|
-
totalBytes += 3; // 基本多语言平面(3字节)
|
|
80
|
-
} else if (code <= 0x10ffff) {
|
|
81
|
-
totalBytes += 4; // 辅助平面(4字节)
|
|
82
|
-
} else {
|
|
83
|
-
totalBytes += 3; // 无效码点,使用替换字符长度
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return totalBytes;
|
|
87
|
-
}
|
|
88
|
-
throw new Error('Unsupported encoding. Use "utf8" or "utf16".');
|
|
89
|
-
}
|
|
90
|
-
//# sourceMappingURL=base.js.map
|
|
1
|
+
"use strict";export function getCurrentTime(){return(new Date).getTime()}export function getStringSize(t,e="utf8"){if("string"!=typeof t)throw new TypeError("Input must be a string");const r=e.toLowerCase();if("utf16"===r){let e=0;for(let r=0;r<t.length;r++){const n=t.charCodeAt(r);if(n>=55296&&n<=56319&&r+1<t.length){const n=t.charCodeAt(r+1);if(n>=56320&&n<=57343){e+=4,r++;continue}}e+=2}return e}if("utf8"===r){let e=0;for(let r=0;r<t.length;r++){let n=t.charCodeAt(r);if(n>=55296&&n<=56319&&r+1<t.length){const e=t.charCodeAt(r+1);e>=56320&&e<=57343&&(n=1024*(n-55296)+(e-56320)+65536,r++)}e+=n<=127?1:n<=2047?2:n<=65535?3:n<=1114111?4:3}return e}throw new Error('Unsupported encoding. Use "utf8" or "utf16".')}
|
|
@@ -1,144 +1 @@
|
|
|
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
|
-
if (count < 0) {
|
|
118
|
-
return `#${index} ... frames omitted (unknown count) ...`;
|
|
119
|
-
}
|
|
120
|
-
return `#${index} ... ${count} frames omitted ...`;
|
|
121
|
-
}
|
|
122
|
-
const library = frame.library || '<unknown>';
|
|
123
|
-
const symbol = frame.symbol || '';
|
|
124
|
-
const address = frame.address || '';
|
|
125
|
-
const offset = frame.offset || '';
|
|
126
|
-
if (symbol) {
|
|
127
|
-
return `#${index} ${library} ${symbol} + ${offset}`;
|
|
128
|
-
}
|
|
129
|
-
return `#${index} ${library} ${address} (${offset})`;
|
|
130
|
-
}).join('\n');
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* 安全调用原生桥方法,异常时静默降级。
|
|
135
|
-
*/
|
|
136
|
-
export async function safeNativeCall(fn, label) {
|
|
137
|
-
try {
|
|
138
|
-
return await fn();
|
|
139
|
-
} catch (e) {
|
|
140
|
-
logger.warn('native-bridge', `${label || 'native call'} failed`, e);
|
|
141
|
-
return undefined;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
//# sourceMappingURL=crash.js.map
|
|
1
|
+
"use strict";import{RumEventType,logger}from"@arms/rum-core";export function convertCrashToRumEvent(e){const t=e.signal?.name||e.exception?.class_name||"NativeCrash",n=buildMessage(e),a=e.frames||e.crashed_thread?.frames||[],i=formatStack(a),s=a[0],r="object"==typeof s&&s?.library||"";let o=0;if(a.length>0){const e=a[0];e&&"object"==typeof e&&"number"==typeof e.line&&(o=e.line)}const c={id:e.context?.view_id||"",name:e.context?.view_name||""};let m=e.timestamp||Date.now();m<1e12&&(m*=1e3);const d=(e.binary_images||[]).map(e=>e.uuid||!e.build_id&&!e.debug_id?e:{...e,uuid:e.build_id||e.debug_id});return{event_type:RumEventType.EXCEPTION,timestamp:m,session_id:e.context?.session_id,view:c,times:1,source:"event",type:"crash",name:t,message:n,file:r,line:o,column:0,stack:i,caused_by:e.exception?.message||(e.signal?`${e.signal.name||""} at ${e.signal.fault_address||""}`:""),binary_images:JSON.stringify(d),snapshots:JSON.stringify({device:e.device||{},registers:e.registers||{},app:e.app||{},crash_context:e.context||{},arch:e.arch||"",pid:e.pid,signal_number:e.signal?.number,thread_id:String(e.crashed_thread?.id??e.tid??""),...e.mechanism?{mechanism:e.mechanism}:{}})}}const OOM_DEFAULT_MESSAGE="App was killed while active with no crash report (suspected watchdog termination)",OOM_DEFAULT_MECHANISM={type:"watchdog_termination",handled:!1};export function convertOomToRumEvent(e){let t=e.timestamp||Date.now();return t<1e12&&(t*=1e3),{event_type:RumEventType.EXCEPTION,timestamp:t,session_id:e.context?.session_id,view:{id:e.context?.view_id||"",name:e.context?.view_name||""},times:1,source:"event",type:"crash",subtype:"oom",name:e.name||"WatchdogTermination",message:e.message||OOM_DEFAULT_MESSAGE,stack:"",snapshots:JSON.stringify({mechanism:e.mechanism||OOM_DEFAULT_MECHANISM,platform:e.platform||"ios",app_state:e.app_state||{},crash_context:e.context||{},estimated:!0})}}function buildMessage(e){if(e.exception?.message)return e.exception.message;const t=e.signal;if(t){const e=t.fault_address||"";return`${t.name||""} (${t.code??""})${e?" at "+e:""}`.trim()}return"Native crash"}function formatStack(e){return e&&0!==e.length?e.map((e,t)=>{if("string"==typeof e)return`#${t} <unknown> ${e} ()`;if(e.truncated){const n=e.omitted_count??-1;return n<0?`#${t} ... frames omitted (unknown count) ...`:`#${t} ... ${n} frames omitted ...`}const n=e.library||"<unknown>",a=e.symbol||"",i=e.address||"",s=e.offset||"";return a?`#${t} ${n} ${a} + ${s}`:`#${t} ${n} ${i} (${s})`}).join("\n"):""}export async function safeNativeCall(e,t){try{return await e()}catch(e){return void logger.warn("native-bridge",`${t||"native call"} failed`,e)}}
|