@arms/rum-reactnative 0.1.2 → 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 +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 +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 +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 +141 -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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { isFunction, RumEventType } from '@arms/rum-core';
|
|
4
4
|
import { addNavigateListener, removeNavigateListener } from "../../utils/view.js";
|
|
5
|
+
import { CRASH_CONTEXT_EVENT } from "../../types/crash.js";
|
|
5
6
|
export default class PvCollector {
|
|
6
7
|
name = 'pv-collector';
|
|
7
8
|
pageMap = {};
|
|
@@ -54,6 +55,16 @@ export default class PvCollector {
|
|
|
54
55
|
loading_type: 'initial_load',
|
|
55
56
|
...page
|
|
56
57
|
});
|
|
58
|
+
|
|
59
|
+
// 同步当前 view 上下文到原生侧(崩溃报告关联用)
|
|
60
|
+
const sessionId = ctx.session?.getSessionId?.() || '';
|
|
61
|
+
const userId = ctx.session?.getUserId?.() || '';
|
|
62
|
+
ctx.emitter.emit(CRASH_CONTEXT_EVENT, {
|
|
63
|
+
session_id: sessionId,
|
|
64
|
+
user_id: userId,
|
|
65
|
+
view_id: page.id,
|
|
66
|
+
view_name: page.name
|
|
67
|
+
});
|
|
57
68
|
};
|
|
58
69
|
getViewName(url, page) {
|
|
59
70
|
let {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Expo config plugin for @arms/rum-reactnative.
|
|
5
|
+
*
|
|
6
|
+
* 在 app.config 中声明后,将 SDK 配置写入 `expoConfig.extra.rumSdk`,运行时可通过
|
|
7
|
+
* `Constants.expoConfig?.extra?.rumSdk` 读取,避免在 JS 入口硬编码 endpoint。
|
|
8
|
+
*
|
|
9
|
+
* app.config.ts 示例:
|
|
10
|
+
* plugins: [['@arms/rum-reactnative', { endpoint: '...', env: 'prod' }]]
|
|
11
|
+
*
|
|
12
|
+
* 仅做配置透传,不注入原生构建设置(podspec / gradle 已由 autolinking 处理)。
|
|
13
|
+
* 零运行时依赖,非 Expo 用户安装本包不会拉入 @expo/* 。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** 本插件触碰到的 expo config 切片,故意放宽以避免对 @expo/config-types 的硬依赖。 */
|
|
17
|
+
|
|
18
|
+
const withRumSdk = (config, props) => {
|
|
19
|
+
const extra = {
|
|
20
|
+
...(config.extra ?? {})
|
|
21
|
+
};
|
|
22
|
+
extra.rumSdk = {
|
|
23
|
+
endpoint: props?.endpoint,
|
|
24
|
+
env: props?.env ?? 'prod',
|
|
25
|
+
version: props?.version
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
...config,
|
|
29
|
+
extra
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export default withRumSdk;
|
|
33
|
+
//# sourceMappingURL=expoConfigPlugin.js.map
|
package/lib/module/index.js
CHANGED
|
@@ -1,20 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { debounce, ONE_SECOND } from '@arms/rum-core';
|
|
4
|
+
import { CRASH_CONTEXT_EVENT } from "../types/crash.js";
|
|
5
|
+
import { getCurView } from "../utils/view.js";
|
|
4
6
|
export default class SessionProcessor {
|
|
5
7
|
name = 'session-processor';
|
|
8
|
+
lastSessionId = '';
|
|
6
9
|
setup(ctx) {
|
|
7
10
|
this.ctx = ctx;
|
|
8
11
|
this.update = debounce(this.update, ONE_SECOND);
|
|
12
|
+
// 延迟到下一个事件循环执行,确保 collectors 已完成 setup 并注册了
|
|
13
|
+
// CRASH_CONTEXT_EVENT 监听器(rum-core 中 processors.setup() 先于
|
|
14
|
+
// collectors.setup() 执行)。消除 session 创建后 1 秒内的 context
|
|
15
|
+
// 同步窗口,同时避免 emit 事件因无监听器而被丢弃
|
|
16
|
+
setTimeout(() => this.syncCrashContext(), 0);
|
|
9
17
|
// sdk.onTouchStart(this.update)
|
|
10
18
|
}
|
|
11
19
|
update = e => {
|
|
12
20
|
if (e && !e.isTrusted) return;
|
|
13
21
|
this.ctx?.session.updateSession();
|
|
22
|
+
this.syncCrashContext();
|
|
14
23
|
};
|
|
15
24
|
process(ctx) {
|
|
16
25
|
this.update();
|
|
17
26
|
return ctx.getRumEvent();
|
|
18
27
|
}
|
|
28
|
+
syncCrashContext() {
|
|
29
|
+
if (!this.ctx) return;
|
|
30
|
+
const sessionId = this.ctx.session.getSessionId();
|
|
31
|
+
if (sessionId === this.lastSessionId) return;
|
|
32
|
+
this.lastSessionId = sessionId;
|
|
33
|
+
const view = getCurView(this.ctx);
|
|
34
|
+
this.ctx.emitter.emit(CRASH_CONTEXT_EVENT, {
|
|
35
|
+
session_id: sessionId,
|
|
36
|
+
user_id: this.ctx.session.getUserId(),
|
|
37
|
+
view_id: view?.id || '',
|
|
38
|
+
view_name: view?.name || ''
|
|
39
|
+
});
|
|
40
|
+
}
|
|
19
41
|
}
|
|
20
42
|
//# sourceMappingURL=session-processor.js.map
|
|
@@ -1,10 +1,70 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { Reporter } from '@arms/rum-core';
|
|
3
|
+
import { Reporter, Telemetry, logger } from '@arms/rum-core';
|
|
4
4
|
import { fixAttrs } from "../utils/platform.js";
|
|
5
|
+
import { CRASH_ID_FIELD, CRASH_REPORT_ACK_EVENT } from "../types/crash.js";
|
|
5
6
|
// @ts-ignore
|
|
6
7
|
import { AppState } from 'react-native';
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* 从 bundle.events 的 snapshots 中提取携带的 crash_id(瞬态字段)。
|
|
11
|
+
* crash_id 由 CrashCollector 注入到 snapshots JSON 字符串中,
|
|
12
|
+
* reporter 提取后用于 ack,并在序列化前从 snapshots 中剥离,不会发送到后端。
|
|
13
|
+
* 兼容旧版降级路径:crash_id 可能残留在事件根级。
|
|
14
|
+
*/
|
|
15
|
+
function extractCrashIds(bundle) {
|
|
16
|
+
const ids = [];
|
|
17
|
+
const events = bundle?.events;
|
|
18
|
+
if (Array.isArray(events)) {
|
|
19
|
+
for (const ev of events) {
|
|
20
|
+
if (!ev) continue;
|
|
21
|
+
// 优先从 snapshots 提取
|
|
22
|
+
let cid;
|
|
23
|
+
try {
|
|
24
|
+
const snapshotsObj = JSON.parse(ev.snapshots);
|
|
25
|
+
cid = snapshotsObj?.[CRASH_ID_FIELD];
|
|
26
|
+
} catch {
|
|
27
|
+
// snapshots 解析失败
|
|
28
|
+
}
|
|
29
|
+
// 兼容旧版降级路径:检查根级
|
|
30
|
+
if (!cid) {
|
|
31
|
+
cid = ev[CRASH_ID_FIELD];
|
|
32
|
+
}
|
|
33
|
+
if (typeof cid === 'string' && cid) ids.push(cid);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return ids;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 序列化前剥离事件上的 crash_id 瞬态字段:
|
|
41
|
+
* - 从 snapshots JSON 字符串中移除;
|
|
42
|
+
* - 兼容旧版降级路径:从事件根级移除。
|
|
43
|
+
*/
|
|
44
|
+
function stripCrashIds(bundle) {
|
|
45
|
+
const events = bundle?.events;
|
|
46
|
+
if (!Array.isArray(events)) return;
|
|
47
|
+
for (const ev of events) {
|
|
48
|
+
if (!ev) continue;
|
|
49
|
+
// 从 snapshots 中移除
|
|
50
|
+
if (typeof ev.snapshots === 'string') {
|
|
51
|
+
try {
|
|
52
|
+
const snapshotsObj = JSON.parse(ev.snapshots);
|
|
53
|
+
if (snapshotsObj && CRASH_ID_FIELD in snapshotsObj) {
|
|
54
|
+
delete snapshotsObj[CRASH_ID_FIELD];
|
|
55
|
+
ev.snapshots = JSON.stringify(snapshotsObj);
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// snapshots 解析失败,跳过
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// 兼容旧版降级路径:从根级移除
|
|
62
|
+
if (CRASH_ID_FIELD in ev) {
|
|
63
|
+
delete ev[CRASH_ID_FIELD];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
8
68
|
/**
|
|
9
69
|
* rn-app reporter
|
|
10
70
|
*/
|
|
@@ -16,8 +76,7 @@ class RNReporter extends Reporter {
|
|
|
16
76
|
try {
|
|
17
77
|
this.appStateListener = AppState.addEventListener('change', this.onAppStateChanged);
|
|
18
78
|
} catch (error) {
|
|
19
|
-
|
|
20
|
-
console.info(error);
|
|
79
|
+
logger.warn('reporter', 'AppState listener binding failed', error);
|
|
21
80
|
}
|
|
22
81
|
}
|
|
23
82
|
init(ctx) {
|
|
@@ -29,26 +88,53 @@ class RNReporter extends Reporter {
|
|
|
29
88
|
if (typeof this.flushEventQueue === 'function') {
|
|
30
89
|
this.flushEventQueue();
|
|
31
90
|
}
|
|
91
|
+
// 后台前 flush 自监控数据
|
|
92
|
+
Telemetry.get()?.flush();
|
|
32
93
|
}
|
|
33
94
|
// else if (nextAppState === 'active') {
|
|
34
95
|
// console.warn('RUM:应用回到前台');
|
|
35
96
|
// }
|
|
36
97
|
};
|
|
37
|
-
request(ctx, bundle) {
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
98
|
+
async request(ctx, bundle) {
|
|
99
|
+
// 先从事件中提取崩溃 crash_id(瞬态字段),用于上报后 ack 删文件
|
|
100
|
+
const crashIds = extractCrashIds(bundle);
|
|
101
|
+
try {
|
|
102
|
+
const batchedEvents = await fixAttrs(bundle);
|
|
103
|
+
// 序列化前剥离瞬态字段,避免发送到后端
|
|
104
|
+
stripCrashIds(batchedEvents);
|
|
105
|
+
const config = ctx.getConfig();
|
|
106
|
+
fetch(config.endpoint, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: {
|
|
109
|
+
'Content-Type': 'text/plain'
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify(batchedEvents)
|
|
112
|
+
}).then(res => {
|
|
113
|
+
this.ackCrashes(ctx, crashIds, !!(res && res.ok));
|
|
114
|
+
}).catch(err => {
|
|
115
|
+
this.catchHandle(err);
|
|
116
|
+
this.ackCrashes(ctx, crashIds, false);
|
|
117
|
+
});
|
|
118
|
+
} catch (err) {
|
|
119
|
+
this.catchHandle(err);
|
|
120
|
+
this.ackCrashes(ctx, crashIds, false);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** 通过共享 emitter 通知崩溃采集器上报结果(成功则删文件,失败保留重试) */
|
|
125
|
+
ackCrashes(ctx, crashIds, success) {
|
|
126
|
+
if (!crashIds.length) return;
|
|
127
|
+
try {
|
|
128
|
+
ctx.emitter?.emit(CRASH_REPORT_ACK_EVENT, {
|
|
129
|
+
crashIds,
|
|
130
|
+
success
|
|
131
|
+
});
|
|
132
|
+
} catch (e) {
|
|
133
|
+
logger.warn('reporter', 'emit crash ack failed', e);
|
|
134
|
+
}
|
|
49
135
|
}
|
|
50
136
|
catchHandle = err => {
|
|
51
|
-
|
|
137
|
+
logger.error('reporter', 'sendRequest failed', err);
|
|
52
138
|
};
|
|
53
139
|
destroy() {
|
|
54
140
|
this.appStateListener?.remove();
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { convertBundlesToLogGroup } from '@arms/rum-core';
|
|
4
|
+
export class RNMonitorReporter {
|
|
5
|
+
constructor(endpoint) {
|
|
6
|
+
this.endpoint = this.buildMonitorEndpoint(endpoint);
|
|
7
|
+
}
|
|
8
|
+
send(data) {
|
|
9
|
+
if (!data || data.length === 0) return;
|
|
10
|
+
try {
|
|
11
|
+
// 使用 rum-core 的 Protobuf 编码器将数据编码为 SLS LogGroup 二进制格式
|
|
12
|
+
const protobufBody = convertBundlesToLogGroup(data);
|
|
13
|
+
fetch(this.endpoint, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: {
|
|
16
|
+
'Content-Type': 'application/x-protobuf',
|
|
17
|
+
'x-log-apiversion': '0.6.0',
|
|
18
|
+
'x-log-compresstype': '',
|
|
19
|
+
'x-log-date': new Date().toUTCString()
|
|
20
|
+
},
|
|
21
|
+
body: protobufBody
|
|
22
|
+
}).catch(() => {
|
|
23
|
+
// 静默处理,避免反馈循环(不调用 logger)
|
|
24
|
+
});
|
|
25
|
+
} catch (_) {
|
|
26
|
+
// 吞掉异常,防止自监控影响宿主应用
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
buildMonitorEndpoint(endpoint) {
|
|
30
|
+
try {
|
|
31
|
+
const url = new URL(endpoint);
|
|
32
|
+
return `${url.origin}/rum/monitor`;
|
|
33
|
+
} catch (_) {
|
|
34
|
+
return endpoint;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=monitor-reporter.js.map
|
package/lib/module/shell.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { Shell } from '@arms/rum-core';
|
|
4
|
-
import
|
|
5
|
-
import ExceptionCollector from "./collector/exception/index.js";
|
|
6
|
-
import ApiCollector from "./collector/resource/api.js";
|
|
3
|
+
import { isFunction, Shell, callMonitored } from '@arms/rum-core';
|
|
4
|
+
import * as collectors from "./collector/index.js";
|
|
7
5
|
import DefaultProcessor from "./processor/default-processor.js";
|
|
8
6
|
import SessionProcessor from "./processor/session-processor.js";
|
|
9
7
|
import Reporter from "./reporter/index.js";
|
|
8
|
+
import { RNMonitorReporter } from "./reporter/monitor-reporter.js";
|
|
10
9
|
import { VERSION } from "./utils/platform.js";
|
|
11
10
|
import { RumSession } from "./utils/session.js";
|
|
12
11
|
import { startTrackingViews, stopTrackingViews } from "./utils/view.js";
|
|
12
|
+
const builtInCollectors = [...Object.values(collectors).filter(Collector => isFunction(Collector))];
|
|
13
13
|
/**
|
|
14
14
|
* 对外导出 shell 层, 所有 shell 层模型的 API 设计约定:
|
|
15
15
|
* 1. API 命名空间按照 variables / functions / events 来组织
|
|
@@ -23,10 +23,12 @@ export class ArmsRum extends Shell {
|
|
|
23
23
|
* 初始化
|
|
24
24
|
*/
|
|
25
25
|
init(config) {
|
|
26
|
-
this.client.useCollectors(
|
|
26
|
+
this.client.useCollectors(builtInCollectors.map(Collector => callMonitored(() => new Collector(), undefined, [], 'shell', `init ${Collector.name}`)).filter(Boolean));
|
|
27
27
|
this.client.useProcessors([new DefaultProcessor(), new SessionProcessor()]);
|
|
28
28
|
this.client.useReporter(new Reporter());
|
|
29
|
-
|
|
29
|
+
// 传入 MonitorReporter 激活自监控
|
|
30
|
+
const monitorReporter = config.endpoint ? new RNMonitorReporter(config.endpoint) : undefined;
|
|
31
|
+
this.client.init(config, new RumSession(), undefined, monitorReporter);
|
|
30
32
|
return this;
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -37,7 +39,6 @@ export class ArmsRum extends Shell {
|
|
|
37
39
|
setConfig(...params) {
|
|
38
40
|
const ctx = this.client.getContext();
|
|
39
41
|
if (params.length === 2) {
|
|
40
|
-
const ctx = this.client.getContext();
|
|
41
42
|
const oldConfig = ctx.getConfig();
|
|
42
43
|
ctx.setConfig({
|
|
43
44
|
...oldConfig,
|
|
@@ -75,6 +76,9 @@ export class ArmsRum extends Shell {
|
|
|
75
76
|
stopTrackingViews() {
|
|
76
77
|
stopTrackingViews();
|
|
77
78
|
}
|
|
79
|
+
getCollector(name) {
|
|
80
|
+
return this.client.getCollectors().find(collector => collector.name === name);
|
|
81
|
+
}
|
|
78
82
|
}
|
|
79
83
|
export default new ArmsRum();
|
|
80
84
|
//# sourceMappingURL=shell.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 崩溃相关类型定义
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** 崩溃上下文同步事件名 */
|
|
8
|
+
export const CRASH_CONTEXT_EVENT = 'crash_context_change';
|
|
9
|
+
|
|
10
|
+
/** 崩溃上报确认事件名(reporter -> crash collector,用于 ack 后删除文件) */
|
|
11
|
+
export const CRASH_REPORT_ACK_EVENT = 'crash_report_ack';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 挂在崩溃 RumEvent 上的瞬态字段名,携带 crash_id。
|
|
15
|
+
* reporter 上报前提取用于 ack、并在序列化前剥离,不会发送到后端。
|
|
16
|
+
*/
|
|
17
|
+
export const CRASH_ID_FIELD = '__rum_crash_id';
|
|
18
|
+
|
|
19
|
+
/** 崩溃上报确认事件负载 */
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 崩溃上下文快照(session / view 变更时同步到原生侧)。
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 原生崩溃栈帧数据结构。
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 原生崩溃数据结构(与原生侧 RumCrashReporter / CrashContextManager 写入的 JSON 对齐)。
|
|
31
|
+
* 仅声明 JS 层会用到的字段,未列出的字段会通过 snapshots 透传给后端。
|
|
32
|
+
*/
|
|
33
|
+
//# sourceMappingURL=crash.js.map
|
|
@@ -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
|