@arms/rum-reactnative 0.1.2 → 0.1.3-beta.1
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 +79 -1
- 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 +436 -0
- package/android/src/main/jni/rum_module_info.c +355 -0
- package/android/src/main/jni/rum_module_info.h +58 -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 +149 -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 +11 -0
- package/lib/module/expoConfigPlugin.js +33 -0
- package/lib/module/index.js +1 -0
- 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 +11 -7
- package/lib/module/types/crash.js +33 -0
- package/lib/module/utils/action.js +299 -0
- package/lib/module/utils/crash.js +144 -0
- package/lib/module/utils/platform.js +124 -72
- package/lib/module/utils/session.js +23 -4
- 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 +6 -0
- 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/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/{src → commonjs/src}/shell.d.ts +11 -0
- 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/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 +51 -28
- 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.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}/collector/view/pv.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/client.d.ts +0 -0
- /package/lib/typescript/{src → commonjs/src}/types/index.d.ts +0 -0
- /package/lib/typescript/{src → commonjs/src}/utils/api.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,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.addNavigateListener = addNavigateListener;
|
|
7
|
+
exports.getCurView = getCurView;
|
|
8
|
+
exports.removeNavigateListener = removeNavigateListener;
|
|
9
|
+
exports.startTrackingViews = startTrackingViews;
|
|
10
|
+
exports.stopTrackingViews = stopTrackingViews;
|
|
11
|
+
var _rumCore = require("@arms/rum-core");
|
|
12
|
+
// import { IPage } from "../types/client";
|
|
13
|
+
|
|
14
|
+
// export const PAGE_ID = '__rum_view_id';
|
|
15
|
+
//
|
|
16
|
+
// /**
|
|
17
|
+
// * 获取 RUM 内置的view_id标识
|
|
18
|
+
// * @param page
|
|
19
|
+
// * @param session
|
|
20
|
+
// * @returns
|
|
21
|
+
// */
|
|
22
|
+
// export async function getRumViewId(page: IPage, session?: IRumSession): Promise<string> {
|
|
23
|
+
// if (!page) return;
|
|
24
|
+
// if (page.id) {
|
|
25
|
+
// return page.id;
|
|
26
|
+
// }
|
|
27
|
+
// if (!session) return;
|
|
28
|
+
// return page.id = session.getViewId();
|
|
29
|
+
// }
|
|
30
|
+
//
|
|
31
|
+
// /**
|
|
32
|
+
// * 获取当前 view
|
|
33
|
+
// * @param viewId
|
|
34
|
+
// * @param ctx
|
|
35
|
+
// * @returns
|
|
36
|
+
// */
|
|
37
|
+
// export function getViewById(viewId: string, ctx: IContext): IViewData {
|
|
38
|
+
// if (!viewId || !ctx) return;
|
|
39
|
+
// return ctx.getViews().find(view => view.id === viewId);
|
|
40
|
+
// }
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 获取当前 view
|
|
44
|
+
* @param ctx
|
|
45
|
+
* @returns
|
|
46
|
+
*/
|
|
47
|
+
function getCurView(ctx) {
|
|
48
|
+
if (!ctx) return;
|
|
49
|
+
const views = ctx.getViews();
|
|
50
|
+
if (views && views.length) {
|
|
51
|
+
return views[views.length - 1];
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
let currentNavigationRef;
|
|
56
|
+
const navigateListeners = [];
|
|
57
|
+
function addNavigateListener(listener) {
|
|
58
|
+
if ((0, _rumCore.isFunction)(listener)) {
|
|
59
|
+
navigateListeners.push(listener);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function removeNavigateListener(listener) {
|
|
63
|
+
navigateListeners.splice(navigateListeners.indexOf(listener), 1);
|
|
64
|
+
}
|
|
65
|
+
function triggerNavigateChange(event) {
|
|
66
|
+
if (!currentNavigationRef) return;
|
|
67
|
+
let route = event;
|
|
68
|
+
if ((0, _rumCore.isFunction)(currentNavigationRef.getCurrentRoute)) {
|
|
69
|
+
route = currentNavigationRef.getCurrentRoute();
|
|
70
|
+
}
|
|
71
|
+
if (!route) return;
|
|
72
|
+
navigateListeners.forEach(listener => {
|
|
73
|
+
listener(route);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function startTrackingViews(nav) {
|
|
77
|
+
if (!nav || currentNavigationRef) return;
|
|
78
|
+
currentNavigationRef = nav;
|
|
79
|
+
if ((0, _rumCore.isFunction)(nav.events)) {
|
|
80
|
+
// react-native-navigation
|
|
81
|
+
nav.events().registerComponentDidAppearListener(triggerNavigateChange);
|
|
82
|
+
} else {
|
|
83
|
+
// @react-native/navigation
|
|
84
|
+
nav.addListener('state', triggerNavigateChange);
|
|
85
|
+
}
|
|
86
|
+
triggerNavigateChange({});
|
|
87
|
+
}
|
|
88
|
+
function stopTrackingViews() {
|
|
89
|
+
if (!currentNavigationRef) return;
|
|
90
|
+
currentNavigationRef.removeListener('state', triggerNavigateChange);
|
|
91
|
+
currentNavigationRef = undefined;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=view.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 原生设备信息,对应存储模型中的公共属性:
|
|
7
|
+
* - device.* (id / type / brand / model / name)
|
|
8
|
+
* - os.* (type / version / container / container_version)
|
|
9
|
+
* - app.package
|
|
10
|
+
* - viewport.width / viewport.height
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 获取原生模块。
|
|
15
|
+
*
|
|
16
|
+
* 使用非强制的 `TurboModuleRegistry.get`(而非 `getEnforcing`),避免在原生
|
|
17
|
+
* 二进制未注册该模块时于「模块加载阶段」直接抛异常——那会连锁导致整个 JS
|
|
18
|
+
* bundle 评估失败(表现为所有路由丢失 default export、App 白屏)。
|
|
19
|
+
*
|
|
20
|
+
* 当原生模块缺失时(通常是新增原生代码后未重新编译原生 App,只重跑了 Metro),
|
|
21
|
+
* 返回一组空实现兜底:崩溃采集与原生设备信息降级为不可用,但 PV / 行为 / API /
|
|
22
|
+
* JS 异常等纯 JS 能力不受影响。需重新构建原生工程(如 `expo run:ios` /
|
|
23
|
+
* `pod install` + 重新编译)后崩溃采集才会真正生效。
|
|
24
|
+
*/
|
|
25
|
+
const nativeModule = TurboModuleRegistry.get('RumSdkReactnative');
|
|
26
|
+
if (!nativeModule) {
|
|
27
|
+
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
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 原生模块缺失时的空实现,保证调用方不会因缺方法而抛错。 */
|
|
31
|
+
const fallback = {
|
|
32
|
+
getItem: async () => null,
|
|
33
|
+
setItem: async () => {},
|
|
34
|
+
removeItem: async () => {},
|
|
35
|
+
clear: async () => {},
|
|
36
|
+
getAllKeys: async () => [],
|
|
37
|
+
getDeviceInfo: async () => ({}),
|
|
38
|
+
getPendingCrashes: async () => '[]',
|
|
39
|
+
clearCrashReports: async () => {},
|
|
40
|
+
clearAllCrashReports: async () => {},
|
|
41
|
+
cleanupOldCrashes: async () => {},
|
|
42
|
+
crashedLastRun: async () => false,
|
|
43
|
+
updateCrashContext: () => {},
|
|
44
|
+
triggerTestCrash: () => {}
|
|
45
|
+
};
|
|
46
|
+
export default nativeModule ?? fallback;
|
|
47
|
+
//# sourceMappingURL=NativeRumSdkReactnative.js.map
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { RumEventType } from '@arms/rum-core';
|
|
4
4
|
import { getCurView } from "../../utils/view.js";
|
|
5
|
+
import { addActionListener, removeActionListener, startTrackingActions, stopTrackingActions } from "../../utils/action.js";
|
|
5
6
|
export default class ActionCollector {
|
|
6
7
|
name = 'action-collector';
|
|
7
8
|
DELAY_TIME = 300;
|
|
@@ -15,7 +16,9 @@ export default class ActionCollector {
|
|
|
15
16
|
}
|
|
16
17
|
this.ctx = ctx;
|
|
17
18
|
this.sendEvent = sendEvent;
|
|
18
|
-
//
|
|
19
|
+
// 开启 React Native 触摸自动采集,并监听标准化的 tap 事件
|
|
20
|
+
startTrackingActions();
|
|
21
|
+
addActionListener(this.onEvent);
|
|
19
22
|
}
|
|
20
23
|
onEvent = e => {
|
|
21
24
|
if (e && this.events.includes(e.type) && e.target) {
|
|
@@ -37,32 +40,64 @@ export default class ActionCollector {
|
|
|
37
40
|
const event = {
|
|
38
41
|
event_type: RumEventType.ACTION,
|
|
39
42
|
type,
|
|
40
|
-
name: id,
|
|
41
|
-
|
|
43
|
+
name: e.name || id,
|
|
44
|
+
target_name: e.targetName,
|
|
45
|
+
duration: e.duration,
|
|
46
|
+
position: e.position,
|
|
47
|
+
target: this.buildTarget(target),
|
|
48
|
+
snapshots: dataset && Object.keys(dataset).length ? JSON.stringify(dataset).slice(0, 1000) : undefined,
|
|
42
49
|
times: 1,
|
|
43
50
|
view
|
|
44
51
|
};
|
|
45
52
|
clearTimeout(this.timer);
|
|
46
|
-
if (this.lastEvent) {
|
|
47
|
-
|
|
53
|
+
if (this.lastEvent && this.lastActionId === id) {
|
|
54
|
+
// 同一元素的连续点击:仅累加次数
|
|
55
|
+
// @ts-ignore
|
|
56
|
+
this.lastEvent.times += 1;
|
|
57
|
+
} else {
|
|
58
|
+
// 切换到不同元素:立即上报上一个,再记录新事件
|
|
59
|
+
if (this.lastEvent) {
|
|
48
60
|
this.sendAction();
|
|
49
|
-
} else {
|
|
50
|
-
// @ts-ignore
|
|
51
|
-
this.lastEvent.times += 1;
|
|
52
61
|
}
|
|
53
|
-
} else {
|
|
54
62
|
this.lastEvent = event;
|
|
63
|
+
this.lastActionId = id;
|
|
55
64
|
}
|
|
56
65
|
this.timer = setTimeout(this.sendAction, this.DELAY_TIME);
|
|
57
66
|
}
|
|
58
67
|
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 组装热力图相关的 target 信息(selector/宽高/可交互性/可信度)。
|
|
71
|
+
* 当没有任何字段时返回 undefined,避免上报空对象。
|
|
72
|
+
*/
|
|
73
|
+
buildTarget(target) {
|
|
74
|
+
const {
|
|
75
|
+
selector,
|
|
76
|
+
width,
|
|
77
|
+
height,
|
|
78
|
+
reaction,
|
|
79
|
+
trust
|
|
80
|
+
} = target;
|
|
81
|
+
if (selector === undefined && width === undefined && height === undefined && reaction === undefined && trust === undefined) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
selector,
|
|
86
|
+
width,
|
|
87
|
+
height,
|
|
88
|
+
reaction,
|
|
89
|
+
trust
|
|
90
|
+
};
|
|
91
|
+
}
|
|
59
92
|
sendAction = () => {
|
|
60
93
|
// @ts-ignore
|
|
61
94
|
this.sendEvent(this.lastEvent);
|
|
62
95
|
this.lastEvent = undefined;
|
|
96
|
+
this.lastActionId = undefined;
|
|
63
97
|
};
|
|
64
98
|
destroy() {
|
|
65
|
-
|
|
99
|
+
removeActionListener(this.onEvent);
|
|
100
|
+
stopTrackingActions();
|
|
66
101
|
}
|
|
67
102
|
}
|
|
68
103
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { logger } from '@arms/rum-core';
|
|
4
|
+
import NativeRumSdkReactnative from "../../NativeRumSdkReactnative.js";
|
|
5
|
+
import { CRASH_CONTEXT_EVENT, CRASH_ID_FIELD, CRASH_REPORT_ACK_EVENT } from "../../types/crash.js";
|
|
6
|
+
import { convertCrashToRumEvent, safeNativeCall } from "../../utils/crash.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 崩溃采集器(Exception 的子类型):
|
|
10
|
+
* 1. 初始化时读取上次运行写入的原生崩溃文件,转换为 RumEvent 通过标准管线上报;
|
|
11
|
+
* 2. 通过 ctx.emitter 订阅上下文变更事件,同步到原生侧
|
|
12
|
+
* (用于下一次启动时崩溃报告携带正确上下文)。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export default class CrashCollector {
|
|
16
|
+
name = 'crash-collector';
|
|
17
|
+
initialized = false;
|
|
18
|
+
/** 已上报待确认(ack)的 crash_id,成功后才删文件 */
|
|
19
|
+
pendingAckIds = new Set();
|
|
20
|
+
setup(ctx, sendEvent) {
|
|
21
|
+
const {
|
|
22
|
+
collectors = {}
|
|
23
|
+
} = ctx.getConfig();
|
|
24
|
+
if (collectors.crash === false) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
this.ctx = ctx;
|
|
28
|
+
this.sendEvent = sendEvent;
|
|
29
|
+
|
|
30
|
+
// 订阅上下文变更事件,同步到原生侧
|
|
31
|
+
ctx.emitter.on(CRASH_CONTEXT_EVENT, this.onCrashContextChange);
|
|
32
|
+
// 订阅上报确认事件:reporter 上报成功后回 ack,再删除对应崩溃文件
|
|
33
|
+
ctx.emitter.on(CRASH_REPORT_ACK_EVENT, this.onCrashReportAck);
|
|
34
|
+
|
|
35
|
+
// 顺序执行:先处理待上报崩溃,再清理旧文件(避免竞态)
|
|
36
|
+
this.processPendingCrashes().then(() => this.cleanupOldCrashes()).catch(e => {
|
|
37
|
+
logger.error('crash-collector', 'Failed to process crash reports', e);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async processPendingCrashes() {
|
|
41
|
+
if (this.initialized) return;
|
|
42
|
+
this.initialized = true;
|
|
43
|
+
|
|
44
|
+
// 不以 crashedLastRun 作为 gating:KSCrash 的 crashedLastLaunch 只在崩溃后的下一次
|
|
45
|
+
// 启动为 true,再下一次正常启动就清零,会导致遗留崩溃文件永远不被处理。
|
|
46
|
+
// 只要磁盘上有 pending 文件就读取并上报。
|
|
47
|
+
await safeNativeCall(() => NativeRumSdkReactnative.crashedLastRun(), 'crashedLastRun');
|
|
48
|
+
const crashesJson = await safeNativeCall(() => NativeRumSdkReactnative.getPendingCrashes(), 'getPendingCrashes');
|
|
49
|
+
if (!crashesJson) return;
|
|
50
|
+
let crashes = [];
|
|
51
|
+
try {
|
|
52
|
+
crashes = JSON.parse(crashesJson) || [];
|
|
53
|
+
} catch (e) {
|
|
54
|
+
logger.error('crash-collector', 'Failed to parse crashes JSON', e);
|
|
55
|
+
// 清理损坏的文件,避免每次启动重复尝试解析
|
|
56
|
+
await safeNativeCall(() => NativeRumSdkReactnative.clearAllCrashReports(), 'clearAllCrashReports');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!Array.isArray(crashes) || crashes.length === 0) return;
|
|
60
|
+
const crashIds = [];
|
|
61
|
+
const hasFilesWithoutId = crashes.some(c => !c.crash_id);
|
|
62
|
+
for (let i = 0; i < crashes.length; i++) {
|
|
63
|
+
try {
|
|
64
|
+
const event = convertCrashToRumEvent(crashes[i]);
|
|
65
|
+
const cid = crashes[i].crash_id;
|
|
66
|
+
// 将瞬态 crash_id 注入 snapshots(而非根级),reporter 上报前从中提取
|
|
67
|
+
// 用于 ack,并在序列化前从 snapshots 中剥离,不会发送到后端
|
|
68
|
+
if (cid) {
|
|
69
|
+
try {
|
|
70
|
+
const snapshotsObj = JSON.parse(event.snapshots);
|
|
71
|
+
snapshotsObj[CRASH_ID_FIELD] = cid;
|
|
72
|
+
event.snapshots = JSON.stringify(snapshotsObj);
|
|
73
|
+
} catch {
|
|
74
|
+
// snapshots 解析失败时降级:仍挂载到根级以保证 ack 机制可用
|
|
75
|
+
event[CRASH_ID_FIELD] = cid;
|
|
76
|
+
}
|
|
77
|
+
this.pendingAckIds.add(cid);
|
|
78
|
+
crashIds.push(cid);
|
|
79
|
+
}
|
|
80
|
+
// 最后一个崩溃事件使用 immediate 立即 flush 整个队列
|
|
81
|
+
const isLast = i === crashes.length - 1;
|
|
82
|
+
this.sendEvent?.(event, isLast ? {
|
|
83
|
+
immediate: true
|
|
84
|
+
} : undefined);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
logger.warn('crash-collector', 'Failed to convert crash', e);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 有 crash_id 的文件:不在此处删除,等 reporter 上报成功后由 onCrashReportAck
|
|
91
|
+
// 删除;上报失败则保留,下次启动重试(cleanupOldCrashes 兜底防止无限增长)。
|
|
92
|
+
|
|
93
|
+
// 无 crash_id 的旧版 NDK 崩溃文件无法 ack:仅当本次没有待 ack 的带 id 文件时
|
|
94
|
+
// 才安全清理(clearAllCrashReports 会删除全部文件,避免误删 ack-pending)。
|
|
95
|
+
if (hasFilesWithoutId && crashIds.length === 0) {
|
|
96
|
+
await safeNativeCall(() => NativeRumSdkReactnative.clearAllCrashReports(), 'clearAllCrashReports');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 兜底清理:删除过期和超限的崩溃文件,防止存储耗尽 */
|
|
101
|
+
async cleanupOldCrashes() {
|
|
102
|
+
// 最多保留 10 个崩溃文件,7 天过期
|
|
103
|
+
const MAX_FILES = 10;
|
|
104
|
+
const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
105
|
+
await safeNativeCall(() => NativeRumSdkReactnative.cleanupOldCrashes(MAX_FILES, MAX_AGE_MS), 'cleanupOldCrashes');
|
|
106
|
+
}
|
|
107
|
+
onCrashContextChange = context => {
|
|
108
|
+
try {
|
|
109
|
+
NativeRumSdkReactnative.updateCrashContext(JSON.stringify(context));
|
|
110
|
+
} catch {
|
|
111
|
+
//
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* reporter 上报确认回调:
|
|
117
|
+
* - 成功:删除对应 crash_id 的崩溃文件(确保上报成功后才删,不丢数据);
|
|
118
|
+
* - 失败:保留文件,下次启动重试,仅从内存待确认集合中移除。
|
|
119
|
+
*/
|
|
120
|
+
onCrashReportAck = ack => {
|
|
121
|
+
if (!ack || !Array.isArray(ack.crashIds) || ack.crashIds.length === 0) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// 只处理本采集器登记过的 id
|
|
125
|
+
const toClear = ack.crashIds.filter(id => this.pendingAckIds.has(id));
|
|
126
|
+
if (toClear.length === 0) return;
|
|
127
|
+
if (!ack.success) {
|
|
128
|
+
// 上报失败:文件保留,从待确认集合移除(下次启动重新登记与上报)
|
|
129
|
+
for (const id of toClear) this.pendingAckIds.delete(id);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
safeNativeCall(() => NativeRumSdkReactnative.clearCrashReports(JSON.stringify(toClear)), 'clearCrashReports').finally(() => {
|
|
133
|
+
for (const id of toClear) this.pendingAckIds.delete(id);
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
destroy() {
|
|
137
|
+
this.ctx?.emitter.off(CRASH_CONTEXT_EVENT, this.onCrashContextChange);
|
|
138
|
+
this.ctx?.emitter.off(CRASH_REPORT_ACK_EVENT, this.onCrashReportAck);
|
|
139
|
+
this.pendingAckIds.clear();
|
|
140
|
+
this.sendEvent = undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=crash.js.map
|
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
import { isArray, interceptFunction, matchList, RumEventType } from '@arms/rum-core';
|
|
4
4
|
export default class ExceptionCollector {
|
|
5
5
|
name = 'exception-collector';
|
|
6
|
+
installed = false;
|
|
7
|
+
/** fatal 去重保护:防止连续 fatal error 重复上报 */
|
|
8
|
+
handlingFatal = false;
|
|
9
|
+
/** 防止 errorHandle 内部 console.error 形成循环 */
|
|
10
|
+
inErrorHandle = false;
|
|
6
11
|
origin = {
|
|
7
12
|
consoleError: console.error,
|
|
8
|
-
originalOnError: null
|
|
13
|
+
originalOnError: null,
|
|
14
|
+
originalOnUnhandledRejection: null
|
|
9
15
|
};
|
|
10
16
|
setup(ctx, sendEvent) {
|
|
11
17
|
this.ctx = ctx;
|
|
@@ -13,6 +19,9 @@ export default class ExceptionCollector {
|
|
|
13
19
|
this.hackOrigin();
|
|
14
20
|
}
|
|
15
21
|
hackOrigin() {
|
|
22
|
+
// 防止重复安装覆盖 originalOnError
|
|
23
|
+
if (this.installed) return;
|
|
24
|
+
this.installed = true;
|
|
16
25
|
const self = this;
|
|
17
26
|
const {
|
|
18
27
|
collectors = {}
|
|
@@ -22,68 +31,142 @@ export default class ExceptionCollector {
|
|
|
22
31
|
this.origin.originalOnError = ErrorUtils.getGlobalHandler?.();
|
|
23
32
|
ErrorUtils.setGlobalHandler(this.onError);
|
|
24
33
|
}
|
|
34
|
+
// Promise rejection: 优先 Hermes,JSC 兜底
|
|
25
35
|
if (HermesInternal?.enablePromiseRejectionTracker && HermesInternal?.hasPromise?.()) {
|
|
26
36
|
HermesInternal.enablePromiseRejectionTracker({
|
|
27
37
|
allRejections: true,
|
|
28
38
|
onUnhandled: this.onUnhandled
|
|
29
39
|
});
|
|
40
|
+
} else {
|
|
41
|
+
// JSC / 其他引擎:监听 unhandledrejection 事件
|
|
42
|
+
this.setupJSCPromiseRejection();
|
|
30
43
|
}
|
|
31
44
|
}
|
|
32
45
|
if (collectors.consoleError !== false) {
|
|
33
46
|
interceptFunction(console, 'error', function (error) {
|
|
47
|
+
if (self.inErrorHandle) return; // 防止循环
|
|
34
48
|
self.errorHandle(error, 'console.error');
|
|
35
49
|
});
|
|
36
50
|
}
|
|
37
51
|
}
|
|
52
|
+
|
|
53
|
+
/** JSC 引擎 Promise rejection 兜底方案 */
|
|
54
|
+
setupJSCPromiseRejection() {
|
|
55
|
+
try {
|
|
56
|
+
if (typeof globalThis !== 'undefined') {
|
|
57
|
+
const self = this;
|
|
58
|
+
this.origin.originalOnUnhandledRejection = globalThis.onunhandledrejection || null;
|
|
59
|
+
globalThis.onunhandledrejection = function (event) {
|
|
60
|
+
if (event?.reason) {
|
|
61
|
+
self.errorHandle(event.reason, 'onUnhandled');
|
|
62
|
+
}
|
|
63
|
+
if (typeof self.origin.originalOnUnhandledRejection === 'function') {
|
|
64
|
+
self.origin.originalOnUnhandledRejection.call(this, event);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// JSC 不支持时不影响主流程
|
|
70
|
+
}
|
|
71
|
+
}
|
|
38
72
|
onError = (e, isFatal) => {
|
|
73
|
+
// fatal 去重保护:防止连续 fatal error 重复上报或死循环
|
|
74
|
+
if (isFatal) {
|
|
75
|
+
if (this.handlingFatal) return;
|
|
76
|
+
this.handlingFatal = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 先采集(确保数据不丢)
|
|
80
|
+
this.errorHandle(e, 'onError');
|
|
81
|
+
|
|
82
|
+
// fatal error:确保事件落盘后再终止进程
|
|
83
|
+
if (isFatal && this.ctx) {
|
|
84
|
+
try {
|
|
85
|
+
const reporter = this.ctx.reporter;
|
|
86
|
+
if (reporter && typeof reporter.flush === 'function') {
|
|
87
|
+
// 使用超时兜底,避免永久阻塞
|
|
88
|
+
Promise.race([reporter.flush(), new Promise(resolve => setTimeout(resolve, 2000))]).finally(() => {
|
|
89
|
+
if (this.origin.originalOnError) {
|
|
90
|
+
this.origin.originalOnError(e, isFatal);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// ignore flush error
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 非 fatal 或 flush 不可用时直接链式调用
|
|
39
101
|
if (this.origin.originalOnError) {
|
|
40
|
-
// @ts-ignore
|
|
41
102
|
this.origin.originalOnError(e, isFatal);
|
|
42
103
|
}
|
|
43
|
-
|
|
104
|
+
if (!isFatal) {
|
|
105
|
+
this.handlingFatal = false;
|
|
106
|
+
}
|
|
44
107
|
};
|
|
45
108
|
onUnhandled = (_id, error) => {
|
|
46
109
|
this.errorHandle(error, 'onUnhandled');
|
|
47
110
|
};
|
|
48
111
|
errorHandle = (error, source = '') => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
112
|
+
if (this.inErrorHandle) return; // 防止循环
|
|
113
|
+
if (error == null) return; // null/undefined 直接跳过
|
|
114
|
+
|
|
115
|
+
this.inErrorHandle = true;
|
|
116
|
+
try {
|
|
117
|
+
let name = '';
|
|
118
|
+
let message = '';
|
|
119
|
+
let file = '';
|
|
120
|
+
let stack = '';
|
|
121
|
+
let line = 0;
|
|
122
|
+
let column = 0;
|
|
123
|
+
if (error instanceof Object) {
|
|
124
|
+
name = String(error.name || error.constructor?.name || 'Error');
|
|
125
|
+
message = String(error.message || '');
|
|
126
|
+
file = String(error.filename || error.fileName || '');
|
|
127
|
+
// stack 可能在 error.stack 或 error.componentStack(React 渲染错误)
|
|
128
|
+
stack = String(error.stack || '');
|
|
129
|
+
if (!stack && error.componentStack) {
|
|
130
|
+
stack = String(error.componentStack);
|
|
131
|
+
}
|
|
132
|
+
line = Number(error.lineno || error.lineNumber || 0);
|
|
133
|
+
column = Number(error.colno || error.columnNumber || 0);
|
|
134
|
+
} else if (typeof error === 'string') {
|
|
135
|
+
stack = error;
|
|
136
|
+
const err = this.getErrorByStack(error);
|
|
137
|
+
if (err) {
|
|
138
|
+
name = err.name || 'Error';
|
|
139
|
+
message = err.message || '';
|
|
140
|
+
} else {
|
|
141
|
+
name = 'Error';
|
|
142
|
+
message = error.substring(0, 200);
|
|
143
|
+
}
|
|
144
|
+
} else if (typeof error === 'number' || typeof error === 'boolean') {
|
|
145
|
+
name = 'Error';
|
|
146
|
+
message = String(error);
|
|
68
147
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
148
|
+
if (!name || !message && !stack || this.errorFilter(error)) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (this.ctx && this.sendEvent) {
|
|
152
|
+
const baseRumEvent = this.ctx.session.getBaseEvent();
|
|
153
|
+
const normalizedSource = source === 'console.error' ? 'console' : 'event';
|
|
154
|
+
this.sendEvent({
|
|
155
|
+
...baseRumEvent,
|
|
156
|
+
event_type: RumEventType.EXCEPTION,
|
|
157
|
+
source: normalizedSource,
|
|
158
|
+
type: 'error',
|
|
159
|
+
name,
|
|
160
|
+
message,
|
|
161
|
+
file,
|
|
162
|
+
stack,
|
|
163
|
+
line,
|
|
164
|
+
column,
|
|
165
|
+
times: 1
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
} finally {
|
|
169
|
+
this.inErrorHandle = false;
|
|
87
170
|
}
|
|
88
171
|
};
|
|
89
172
|
|
|
@@ -123,13 +206,28 @@ export default class ExceptionCollector {
|
|
|
123
206
|
return matchList(filters, err.name, true, err) || matchList(filters, err.message, true, err) || matchList(filters, err.stack || '', true, err);
|
|
124
207
|
}
|
|
125
208
|
destroy() {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
209
|
+
if (!this.installed) return;
|
|
210
|
+
// 恢复 console.error
|
|
211
|
+
if (this.origin.consoleError) {
|
|
212
|
+
console.error = this.origin.consoleError;
|
|
213
|
+
}
|
|
214
|
+
// 恢复 ErrorUtils.setGlobalHandler
|
|
215
|
+
if (ErrorUtils && this.origin.originalOnError) {
|
|
216
|
+
try {
|
|
217
|
+
ErrorUtils.setGlobalHandler(this.origin.originalOnError);
|
|
218
|
+
} catch {
|
|
219
|
+
// ignore
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// 恢复 onunhandledrejection
|
|
223
|
+
if (typeof globalThis !== 'undefined' && this.origin.originalOnUnhandledRejection !== null) {
|
|
224
|
+
try {
|
|
225
|
+
globalThis.onunhandledrejection = this.origin.originalOnUnhandledRejection;
|
|
226
|
+
} catch {
|
|
227
|
+
// ignore
|
|
228
|
+
}
|
|
129
229
|
}
|
|
130
|
-
|
|
131
|
-
// sdk.offError(this.onError);
|
|
132
|
-
// }
|
|
230
|
+
this.installed = false;
|
|
133
231
|
}
|
|
134
232
|
}
|
|
135
233
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
export { default as PvCollector } from "./view/pv.js";
|
|
4
|
+
export { default as ExceptionCollector } from "./exception/index.js";
|
|
5
|
+
export { default as CrashCollector } from "./exception/crash.js";
|
|
6
|
+
export { default as ApiCollector } from "./resource/api.js";
|
|
7
|
+
export { default as ActionCollector } from "./action/index.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { find, generateGUID, generateSpanId, generateTraceId, isString, interceptFunction, restoreFunction, isFunction, makeTracingHeaders, matchList, parseTracingOptions, performDraw, RumEventType, ResourceStatus, urlMatch } from '@arms/rum-core';
|
|
3
|
+
import { find, generateGUID, generateSpanId, generateTraceId, isString, interceptFunction, restoreFunction, isFunction, makeTracingHeaders, matchList, parseTracingOptions, performDraw, RumEventType, ResourceStatus, urlMatch, logger } from '@arms/rum-core';
|
|
4
4
|
import { getCurrentTime } from "../../utils/base.js";
|
|
5
5
|
import { ApiType, reviseApiAttr, getResourceName, getResourceSuccessByCode } from "../../utils/api.js";
|
|
6
6
|
export default class ApiCollector {
|
|
@@ -32,9 +32,9 @@ export default class ApiCollector {
|
|
|
32
32
|
} = parseTracingOptions(tracing);
|
|
33
33
|
if (!enable) return;
|
|
34
34
|
// @ts-ignore
|
|
35
|
-
const
|
|
36
|
-
if (!
|
|
37
|
-
let pTypes =
|
|
35
|
+
const resolvedTraceOption = find(allowedUrls, item => matchList([item.match], apiAttr.url || ''));
|
|
36
|
+
if (!resolvedTraceOption) return;
|
|
37
|
+
let pTypes = resolvedTraceOption.propagatorTypes;
|
|
38
38
|
if (pTypes && pTypes.length === 0) {
|
|
39
39
|
pTypes = propagatorTypes;
|
|
40
40
|
}
|
|
@@ -141,7 +141,7 @@ export default class ApiCollector {
|
|
|
141
141
|
...reviseApiAttr(attrs)
|
|
142
142
|
};
|
|
143
143
|
} catch (e) {
|
|
144
|
-
|
|
144
|
+
logger.warn('api-collector', 'evaluateApi failed for XHR', e);
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
self.sendAPI(apiAttr, entry);
|
|
@@ -211,7 +211,7 @@ export default class ApiCollector {
|
|
|
211
211
|
...reviseApiAttr(attrs)
|
|
212
212
|
};
|
|
213
213
|
} catch (e) {
|
|
214
|
-
|
|
214
|
+
logger.warn('api-collector', 'evaluateApi failed for fetch', e);
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
self.sendAPI(apiAttr, entry);
|