@arms/rum-reactnative 0.1.3-beta.5 → 0.1.4-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.
Files changed (83) hide show
  1. package/README.md +6 -0
  2. package/android/src/main/java/com/rumsdkreactnative/RumSdkReactnativeModule.kt +31 -0
  3. package/docs/ReactNative SDK/351/205/215/347/275/256/345/217/202/350/200/203.md" +25 -0
  4. package/docs/SDK /347/211/210/346/234/254/350/257/264/346/230/216.md" +14 -0
  5. package/ios/RumAppStateRecorder.h +69 -0
  6. package/ios/RumAppStateRecorder.mm +547 -0
  7. package/ios/RumCrashReporter.h +32 -0
  8. package/ios/RumCrashReporter.mm +97 -11
  9. package/ios/RumSdkReactnative.mm +81 -1
  10. package/lib/commonjs/NativeRumSdkReactnative.js +1 -54
  11. package/lib/commonjs/collector/action/index.js +1 -108
  12. package/lib/commonjs/collector/exception/crash.js +1 -148
  13. package/lib/commonjs/collector/exception/index.js +1 -238
  14. package/lib/commonjs/collector/exception/oom.js +1 -0
  15. package/lib/commonjs/collector/index.js +1 -42
  16. package/lib/commonjs/collector/resource/api.js +1 -292
  17. package/lib/commonjs/collector/view/perf.js +1 -79
  18. package/lib/commonjs/collector/view/pv.js +1 -88
  19. package/lib/commonjs/expoConfigPlugin.js +1 -37
  20. package/lib/commonjs/index.js +1 -29
  21. package/lib/commonjs/plugin.js +1 -47
  22. package/lib/commonjs/processor/default-processor.js +1 -27
  23. package/lib/commonjs/processor/session-processor.js +1 -47
  24. package/lib/commonjs/reporter/index.js +1 -148
  25. package/lib/commonjs/reporter/monitor-reporter.js +1 -43
  26. package/lib/commonjs/shell.js +1 -91
  27. package/lib/commonjs/types/client.js +1 -6
  28. package/lib/commonjs/types/crash.js +1 -37
  29. package/lib/commonjs/types/index.js +1 -2
  30. package/lib/commonjs/types/oom.js +1 -0
  31. package/lib/commonjs/utils/action.js +1 -305
  32. package/lib/commonjs/utils/api.js +1 -84
  33. package/lib/commonjs/utils/base.js +1 -95
  34. package/lib/commonjs/utils/crash.js +1 -149
  35. package/lib/commonjs/utils/platform.js +1 -162
  36. package/lib/commonjs/utils/session.js +1 -158
  37. package/lib/commonjs/utils/url.js +1 -59
  38. package/lib/commonjs/utils/view.js +1 -93
  39. package/lib/module/NativeRumSdkReactnative.js +1 -51
  40. package/lib/module/collector/action/index.js +1 -103
  41. package/lib/module/collector/exception/crash.js +1 -143
  42. package/lib/module/collector/exception/index.js +1 -233
  43. package/lib/module/collector/exception/oom.js +1 -0
  44. package/lib/module/collector/index.js +1 -8
  45. package/lib/module/collector/resource/api.js +1 -287
  46. package/lib/module/collector/view/perf.js +1 -79
  47. package/lib/module/collector/view/pv.js +1 -83
  48. package/lib/module/expoConfigPlugin.js +1 -33
  49. package/lib/module/index.js +1 -7
  50. package/lib/module/plugin.js +1 -42
  51. package/lib/module/processor/default-processor.js +1 -22
  52. package/lib/module/processor/session-processor.js +1 -42
  53. package/lib/module/reporter/index.js +1 -144
  54. package/lib/module/reporter/monitor-reporter.js +1 -38
  55. package/lib/module/shell.js +1 -84
  56. package/lib/module/types/client.js +1 -4
  57. package/lib/module/types/crash.js +1 -33
  58. package/lib/module/types/index.js +1 -2
  59. package/lib/module/types/oom.js +1 -0
  60. package/lib/module/utils/action.js +1 -299
  61. package/lib/module/utils/api.js +1 -76
  62. package/lib/module/utils/base.js +1 -90
  63. package/lib/module/utils/crash.js +1 -144
  64. package/lib/module/utils/platform.js +1 -150
  65. package/lib/module/utils/session.js +1 -154
  66. package/lib/module/utils/url.js +1 -54
  67. package/lib/module/utils/view.js +1 -88
  68. package/lib/typescript/commonjs/src/NativeRumSdkReactnative.d.ts +10 -0
  69. package/lib/typescript/commonjs/src/collector/exception/oom.d.ts +34 -0
  70. package/lib/typescript/commonjs/src/collector/index.d.ts +1 -0
  71. package/lib/typescript/commonjs/src/shell.d.ts +1 -1
  72. package/lib/typescript/commonjs/src/types/client.d.ts +27 -0
  73. package/lib/typescript/commonjs/src/types/oom.d.ts +58 -0
  74. package/lib/typescript/commonjs/src/utils/crash.d.ts +13 -0
  75. package/lib/typescript/module/src/NativeRumSdkReactnative.d.ts +10 -0
  76. package/lib/typescript/module/src/collector/exception/oom.d.ts +34 -0
  77. package/lib/typescript/module/src/collector/index.d.ts +1 -0
  78. package/lib/typescript/module/src/shell.d.ts +1 -1
  79. package/lib/typescript/module/src/types/client.d.ts +27 -0
  80. package/lib/typescript/module/src/types/oom.d.ts +58 -0
  81. package/lib/typescript/module/src/utils/crash.d.ts +13 -0
  82. package/package.json +1 -2
  83. package/src/NativeRumSdkReactnative.ts +18 -0
@@ -29,14 +29,6 @@ static NSString *const kRumCrashedLastRunFlagName = @"crashed_last_run.flag";
29
29
  /** 旧版目录名(用于升级迁移) */
30
30
  static NSString *const kLegacyCrashDirName = @"ARMSCrashReporter";
31
31
 
32
- /** Forward-declare private class methods used by the constructor */
33
- @interface RumCrashReporter (Private)
34
- + (NSString *)crashDir;
35
- + (NSString *)contextPath;
36
- + (id)readJSONFileAtPath:(NSString *)path;
37
- + (void)atomicWriteJSONObject:(id)obj toPath:(NSString *)path;
38
- @end
39
-
40
32
  #pragma mark - Early constructor
41
33
 
42
34
  /**
@@ -224,9 +216,9 @@ static BOOL RumIsDetoxRuntimePresent(void) {
224
216
  // 优先读 flag file —— KSCrash.crashedLastLaunch 在 reports 被清理后会被重置,
225
217
  // 而 SDK 会在 app 启动早期就处理 pending 文件,因此直接读 KSCrash 会导致
226
218
  // 用户在 UI 上查询时永远得到 NO。
227
- NSString *flagPath = [[self crashDir]
228
- stringByAppendingPathComponent:kRumCrashedLastRunFlagName];
229
- if ([[NSFileManager defaultManager] fileExistsAtPath:flagPath]) {
219
+ // flag 生命周期由 install 全权管理(每次启动按 KSCrash.crashedLastLaunch
220
+ // 重写/删除),OOM 判定只读不清除,本启动内查询结果稳定。
221
+ if ([self crashedLastRunFlagExists]) {
230
222
  return YES;
231
223
  }
232
224
  @try {
@@ -426,6 +418,100 @@ static BOOL RumIsDetoxRuntimePresent(void) {
426
418
  }
427
419
  }
428
420
 
421
+ #pragma mark - OOM support
422
+
423
+ + (NSDictionary *)currentCrashContext {
424
+ id obj = [self readJSONFileAtPath:[self contextPath]];
425
+ return [obj isKindOfClass:[NSDictionary class]] ? (NSDictionary *)obj : @{};
426
+ }
427
+
428
+ + (BOOL)crashedLastRunFlagExists {
429
+ NSString *flagPath = [[self crashDir]
430
+ stringByAppendingPathComponent:kRumCrashedLastRunFlagName];
431
+ return [[NSFileManager defaultManager] fileExistsAtPath:flagPath];
432
+ }
433
+
434
+ + (BOOL)hasPendingCrashReports {
435
+ // 轻量检查(只看 reportIDs 数量,不解析报告内容),供 OOM 判定层互斥使用
436
+ @try {
437
+ KSCrashReportStore *store = [KSCrash sharedInstance].reportStore;
438
+ return store != nil && store.reportIDs.count > 0;
439
+ } @catch (...) {
440
+ return NO;
441
+ }
442
+ }
443
+
444
+ + (BOOL)hasPendingCrashReportsAfterEpochMs:(long long)epochMs {
445
+ // 条件⑩时间范围限定:仅当上次运行内产生了崩溃(最新 pending 报告时间戳晚于
446
+ // 上次 app_state.json 的 updatedAt)才抑制 OOM 判定;早于该时间的陈旧报告
447
+ // (离线堆积、crash 关闭时遗留——crash 关闭时 KSCrash 报告只写不删,唯一
448
+ // 清理入口 cleanupOldCrashes 在 CrashCollector.setup 链内)不抑制。
449
+ // 时间戳不可得或比较异常时返回 YES(保守兜底,维持抑制)。
450
+ @try {
451
+ KSCrashReportStore *store = [KSCrash sharedInstance].reportStore;
452
+ if (store == nil) {
453
+ return NO;
454
+ }
455
+ NSArray<NSNumber *> *reportIDs = store.reportIDs;
456
+ if (reportIDs.count == 0) {
457
+ return NO;
458
+ }
459
+ // 基准时刻不可得(epochMs <= 0):退化为"任一 pending 报告即抑制"(原语义)
460
+ if (epochMs <= 0) {
461
+ return YES;
462
+ }
463
+ // 取最新 pending 报告时间戳;单份解析失败按当前时间计(保守视为新鲜崩溃)
464
+ double now = [[NSDate date] timeIntervalSince1970] * 1000.0;
465
+ double newest = 0;
466
+ for (NSNumber *reportID in reportIDs) {
467
+ @autoreleasepool {
468
+ KSCrashReportDictionary *report = [store reportForID:[reportID longLongValue]];
469
+ NSDictionary *raw = report ? report.value : nil;
470
+ double ts = [self crashReportTimestampMsFromRaw:raw fallback:now];
471
+ if (ts > newest) {
472
+ newest = ts;
473
+ }
474
+ }
475
+ }
476
+ return newest > (double)epochMs;
477
+ } @catch (NSException *e) {
478
+ NSLog(@"[RumCrashReporter] hasPendingCrashReportsAfterEpochMs exception: %@", e);
479
+ return YES;
480
+ }
481
+ }
482
+
483
+ /** 解析 KSCrash raw 报告 report.timestamp(ISO8601 字符串或秒级 NSNumber)为毫秒,不可得时返回 fallback(解析模式与 cleanupOldCrashesWithMaxFiles 一致) */
484
+ + (double)crashReportTimestampMsFromRaw:(NSDictionary *)raw fallback:(double)fallback {
485
+ NSDictionary *reportMeta = [raw isKindOfClass:[NSDictionary class]] ? raw[@"report"] : nil;
486
+ if (![reportMeta isKindOfClass:[NSDictionary class]]) {
487
+ return fallback;
488
+ }
489
+ id t = reportMeta[@"timestamp"];
490
+ if ([t isKindOfClass:[NSString class]]) {
491
+ NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
492
+ fmt.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
493
+ NSArray<NSString *> *formats = @[
494
+ @"yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
495
+ @"yyyy-MM-dd'T'HH:mm:ss.SSSZ",
496
+ @"yyyy-MM-dd'T'HH:mm:ss'Z'",
497
+ @"yyyy-MM-dd'T'HH:mm:ssXXX"
498
+ ];
499
+ for (NSString *format in formats) {
500
+ fmt.dateFormat = format;
501
+ NSDate *d = [fmt dateFromString:(NSString *)t];
502
+ if (d != nil) {
503
+ return [d timeIntervalSince1970] * 1000.0;
504
+ }
505
+ }
506
+ return fallback;
507
+ }
508
+ if ([t isKindOfClass:[NSNumber class]]) {
509
+ // 已是数值(秒级),转毫秒
510
+ return [(NSNumber *)t doubleValue] * 1000.0;
511
+ }
512
+ return fallback;
513
+ }
514
+
429
515
  #pragma mark - Context
430
516
 
431
517
  + (void)updateCrashContext:(NSString *)context {
@@ -1,5 +1,6 @@
1
1
  #import "RumSdkReactnative.h"
2
2
  #import "RumCrashReporter.h"
3
+ #import "RumAppStateRecorder.h"
3
4
  #import <UIKit/UIKit.h>
4
5
  #import <sys/utsname.h>
5
6
  #import <signal.h>
@@ -15,7 +16,10 @@ RCT_EXPORT_MODULE()
15
16
  - (instancetype)init
16
17
  {
17
18
  if ((self = [super init])) {
18
- // 模块实例化时安装崩溃监控(dispatch_once 内部保证幂等)
19
+ // 模块实例化时安装崩溃监控(dispatch_once 内部保证幂等)。
20
+ // OOM(看门狗终止)采集不在此时安装:由 updateCollectorsConfig 驱动
21
+ // (JS 侧 OomCollector 在 collectors.oom === true 时调用),未调用即
22
+ // 未开启,不产生任何 OOM 相关 I/O
19
23
  [RumCrashReporter install];
20
24
  }
21
25
  return self;
@@ -244,6 +248,82 @@ RCT_EXPORT_MODULE()
244
248
  resolve(@([RumCrashReporter crashedLastRun]));
245
249
  }
246
250
 
251
+ // MARK: - OOM (Watchdog Termination) Reporting
252
+
253
+ - (void)updateCollectorsConfig:(NSString *)config
254
+ {
255
+ @try {
256
+ // 解析 config JSON(如 {"oom": true})。仅 oom == true 时安装 OOM 状态记录器
257
+ // 并执行上次启动的看门狗终止判定(setup 内部 dispatch_once 幂等,重复调用
258
+ // 不重复判定/安装);false/缺省/解析失败 = no-op,不安装、不判定、不产生
259
+ // 任何 OOM 相关 I/O(D6 默认关闭)
260
+ BOOL oomEnabled = NO;
261
+ if (config.length > 0) {
262
+ NSData *data = [config dataUsingEncoding:NSUTF8StringEncoding];
263
+ id parsed = data ? [NSJSONSerialization JSONObjectWithData:data
264
+ options:0
265
+ error:nil]
266
+ : nil;
267
+ if ([parsed isKindOfClass:[NSDictionary class]]) {
268
+ id oom = [(NSDictionary *)parsed objectForKey:@"oom"];
269
+ if ([oom isKindOfClass:[NSNumber class]]) {
270
+ oomEnabled = [(NSNumber *)oom boolValue];
271
+ }
272
+ }
273
+ }
274
+ if (oomEnabled) {
275
+ [RumAppStateRecorder setup];
276
+ }
277
+ } @catch (NSException *exception) {
278
+ // void 返回的 codegen 方法无 reject 通道,静默降级(对齐 JS 侧 try-catch 兜底)
279
+ NSLog(@"[RumSdkReactnative] updateCollectorsConfig exception: %@", exception);
280
+ }
281
+ }
282
+
283
+ - (void)getPendingOomReports:(RCTPromiseResolveBlock)resolve
284
+ reject:(RCTPromiseRejectBlock)reject
285
+ {
286
+ @try {
287
+ NSArray<NSDictionary *> *reports = [RumAppStateRecorder pendingOomReports];
288
+ NSError *error = nil;
289
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:reports
290
+ options:0
291
+ error:&error];
292
+ if (jsonData == nil) {
293
+ resolve(@"[]");
294
+ return;
295
+ }
296
+ NSString *jsonString = [[NSString alloc] initWithData:jsonData
297
+ encoding:NSUTF8StringEncoding];
298
+ resolve(jsonString ?: @"[]");
299
+ } @catch (NSException *exception) {
300
+ reject(@"RUM_OOM_GET_PENDING_ERROR", exception.reason, nil);
301
+ }
302
+ }
303
+
304
+ - (void)clearOomReports:(NSString *)oomIds
305
+ resolve:(RCTPromiseResolveBlock)resolve
306
+ reject:(RCTPromiseRejectBlock)reject
307
+ {
308
+ @try {
309
+ NSArray *ids = @[];
310
+ if (oomIds.length > 0) {
311
+ NSData *data = [oomIds dataUsingEncoding:NSUTF8StringEncoding];
312
+ id parsed = data ? [NSJSONSerialization JSONObjectWithData:data
313
+ options:0
314
+ error:nil]
315
+ : nil;
316
+ if ([parsed isKindOfClass:[NSArray class]]) {
317
+ ids = (NSArray *)parsed;
318
+ }
319
+ }
320
+ [RumAppStateRecorder clearOomReportsWithIds:ids];
321
+ resolve(nil);
322
+ } @catch (NSException *exception) {
323
+ reject(@"RUM_OOM_CLEAR_ERROR", exception.reason, nil);
324
+ }
325
+ }
326
+
247
327
  - (void)updateCrashContext:(NSString *)context
248
328
  {
249
329
  [RumCrashReporter updateCrashContext:context];
@@ -1,54 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- var _reactNative = require("react-native");
8
- /**
9
- * 原生设备信息,对应存储模型中的公共属性:
10
- * - device.* (id / type / brand / model / name)
11
- * - os.* (type / version / container / container_version)
12
- * - app.package
13
- * - viewport.width / viewport.height
14
- */
15
-
16
- /**
17
- * 获取原生模块。
18
- *
19
- * 优先使用 TurboModuleRegistry(RN 0.68+ TurboModule 架构),
20
- * 若未找到则降级到 Legacy Bridge 的 NativeModules(RN 0.68-0.70 未开启 New Arch 时),
21
- * 若均不可用则返回一组空实现兜底。
22
- *
23
- * 使用非强制的 `TurboModuleRegistry.get`(而非 `getEnforcing`),避免在原生
24
- * 二进制未注册该模块时于「模块加载阶段」直接抛异常——那会连锁导致整个 JS
25
- * bundle 评估失败(表现为所有路由丢失 default export、App 白屏)。
26
- *
27
- * 当原生模块缺失时(通常是新增原生代码后未重新编译原生 App,只重跑了 Metro),
28
- * 返回一组空实现兜底:崩溃采集与原生设备信息降级为不可用,但 PV / 行为 / API /
29
- * JS 异常等纯 JS 能力不受影响。需重新构建原生工程(如 `expo run:ios` /
30
- * `pod install` + 重新编译)后崩溃采集才会真正生效。
31
- */
32
- const nativeModule = _reactNative.TurboModuleRegistry.get('RumSdkReactnative') ?? _reactNative.NativeModules.RumSdkReactnative;
33
- if (!nativeModule) {
34
- 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.');
35
- }
36
-
37
- /** 原生模块缺失时的空实现,保证调用方不会因缺方法而抛错。 */
38
- const fallback = {
39
- getItem: async () => null,
40
- setItem: async () => {},
41
- removeItem: async () => {},
42
- clear: async () => {},
43
- getAllKeys: async () => [],
44
- getDeviceInfo: async () => ({}),
45
- getPendingCrashes: async () => '[]',
46
- clearCrashReports: async () => {},
47
- clearAllCrashReports: async () => {},
48
- cleanupOldCrashes: async () => {},
49
- crashedLastRun: async () => false,
50
- updateCrashContext: () => {},
51
- triggerTestCrash: () => {}
52
- };
53
- var _default = exports.default = nativeModule ?? fallback;
54
- //# sourceMappingURL=NativeRumSdkReactnative.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var _reactNative=require("react-native");const nativeModule=_reactNative.TurboModuleRegistry.get("RumSdkReactnative")??_reactNative.NativeModules.RumSdkReactnative;nativeModule||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.");const fallback={getItem:async()=>null,setItem:async()=>{},removeItem:async()=>{},clear:async()=>{},getAllKeys:async()=>[],getDeviceInfo:async()=>({}),getPendingCrashes:async()=>"[]",clearCrashReports:async()=>{},clearAllCrashReports:async()=>{},cleanupOldCrashes:async()=>{},crashedLastRun:async()=>!1,updateCrashContext:()=>{},triggerTestCrash:()=>{},getPendingOomReports:async()=>"[]",clearOomReports:async()=>{},updateCollectorsConfig:()=>{}};var _default=exports.default=nativeModule??fallback;
@@ -1,108 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- var _rumCore = require("@arms/rum-core");
8
- var _view = require("../../utils/view");
9
- var _action = require("../../utils/action");
10
- class ActionCollector {
11
- name = 'action-collector';
12
- DELAY_TIME = 300;
13
- events = ['tap'];
14
- setup(ctx, sendEvent) {
15
- const {
16
- collectors = {}
17
- } = ctx.getConfig();
18
- if (collectors.action === false) {
19
- return;
20
- }
21
- this.ctx = ctx;
22
- this.sendEvent = sendEvent;
23
- // 开启 React Native 触摸自动采集,并监听标准化的 tap 事件
24
- (0, _action.startTrackingActions)();
25
- (0, _action.addActionListener)(this.onEvent);
26
- }
27
- onEvent = e => {
28
- if (e && this.events.includes(e.type) && e.target) {
29
- const {
30
- target,
31
- currentTarget,
32
- type
33
- } = e;
34
- const {
35
- dataset,
36
- id
37
- } = target;
38
- if (!id) return;
39
- if (currentTarget && id !== currentTarget.id) return;
40
- let view;
41
- if (this.ctx) {
42
- view = (0, _view.getCurView)(this.ctx);
43
- }
44
- const event = {
45
- event_type: _rumCore.RumEventType.ACTION,
46
- type,
47
- name: e.name || id,
48
- target_name: e.targetName,
49
- duration: e.duration,
50
- position: e.position,
51
- target: this.buildTarget(target),
52
- snapshots: dataset && Object.keys(dataset).length ? JSON.stringify(dataset).slice(0, 1000) : undefined,
53
- times: 1,
54
- view
55
- };
56
- clearTimeout(this.timer);
57
- if (this.lastEvent && this.lastActionId === id) {
58
- // 同一元素的连续点击:仅累加次数
59
- // @ts-ignore
60
- this.lastEvent.times += 1;
61
- } else {
62
- // 切换到不同元素:立即上报上一个,再记录新事件
63
- if (this.lastEvent) {
64
- this.sendAction();
65
- }
66
- this.lastEvent = event;
67
- this.lastActionId = id;
68
- }
69
- this.timer = setTimeout(this.sendAction, this.DELAY_TIME);
70
- }
71
- };
72
-
73
- /**
74
- * 组装热力图相关的 target 信息(selector/宽高/可交互性/可信度)。
75
- * 当没有任何字段时返回 undefined,避免上报空对象。
76
- */
77
- buildTarget(target) {
78
- const {
79
- selector,
80
- width,
81
- height,
82
- reaction,
83
- trust
84
- } = target;
85
- if (selector === undefined && width === undefined && height === undefined && reaction === undefined && trust === undefined) {
86
- return undefined;
87
- }
88
- return {
89
- selector,
90
- width,
91
- height,
92
- reaction,
93
- trust
94
- };
95
- }
96
- sendAction = () => {
97
- // @ts-ignore
98
- this.sendEvent(this.lastEvent);
99
- this.lastEvent = undefined;
100
- this.lastActionId = undefined;
101
- };
102
- destroy() {
103
- (0, _action.removeActionListener)(this.onEvent);
104
- (0, _action.stopTrackingActions)();
105
- }
106
- }
107
- exports.default = ActionCollector;
108
- //# sourceMappingURL=index.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var _rumCore=require("@arms/rum-core"),_view=require("../../utils/view"),_action=require("../../utils/action");class ActionCollector{name="action-collector";DELAY_TIME=300;events=["tap"];setup(t,e){const{collectors:i={}}=t.getConfig();!1!==i.action&&(this.ctx=t,this.sendEvent=e,(0,_action.startTrackingActions)(),(0,_action.addActionListener)(this.onEvent))}onEvent=t=>{if(t&&this.events.includes(t.type)&&t.target){const{target:e,currentTarget:i,type:s}=t,{dataset:n,id:o}=e;if(!o)return;if(i&&o!==i.id)return;let r;this.ctx&&(r=(0,_view.getCurView)(this.ctx));const a={event_type:_rumCore.RumEventType.ACTION,type:s,name:t.name||o,target_name:t.targetName,duration:t.duration,position:t.position,target:this.buildTarget(e),snapshots:n&&Object.keys(n).length?JSON.stringify(n).slice(0,1e3):void 0,times:1,view:r};clearTimeout(this.timer),this.lastEvent&&this.lastActionId===o?this.lastEvent.times+=1:(this.lastEvent&&this.sendAction(),this.lastEvent=a,this.lastActionId=o),this.timer=setTimeout(this.sendAction,this.DELAY_TIME)}};buildTarget(t){const{selector:e,width:i,height:s,reaction:n,trust:o}=t;if(void 0!==e||void 0!==i||void 0!==s||void 0!==n||void 0!==o)return{selector:e,width:i,height:s,reaction:n,trust:o}}sendAction=()=>{this.sendEvent(this.lastEvent),this.lastEvent=void 0,this.lastActionId=void 0};destroy(){(0,_action.removeActionListener)(this.onEvent),(0,_action.stopTrackingActions)()}}exports.default=ActionCollector;
@@ -1,148 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- var _rumCore = require("@arms/rum-core");
8
- var _NativeRumSdkReactnative = _interopRequireDefault(require("../../NativeRumSdkReactnative"));
9
- var _crash = require("../../types/crash");
10
- var _crash2 = require("../../utils/crash");
11
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
- /**
13
- * 崩溃采集器(Exception 的子类型):
14
- * 1. 初始化时读取上次运行写入的原生崩溃文件,转换为 RumEvent 通过标准管线上报;
15
- * 2. 通过 ctx.emitter 订阅上下文变更事件,同步到原生侧
16
- * (用于下一次启动时崩溃报告携带正确上下文)。
17
- */
18
-
19
- class CrashCollector {
20
- name = 'crash-collector';
21
- initialized = false;
22
- /** 已上报待确认(ack)的 crash_id,成功后才删文件 */
23
- pendingAckIds = new Set();
24
- setup(ctx, sendEvent) {
25
- const {
26
- collectors = {}
27
- } = ctx.getConfig();
28
- if (collectors.crash === false) {
29
- return;
30
- }
31
- this.ctx = ctx;
32
- this.sendEvent = sendEvent;
33
-
34
- // 订阅上下文变更事件,同步到原生侧
35
- ctx.emitter.on(_crash.CRASH_CONTEXT_EVENT, this.onCrashContextChange);
36
- // 订阅上报确认事件:reporter 上报成功后回 ack,再删除对应崩溃文件
37
- ctx.emitter.on(_crash.CRASH_REPORT_ACK_EVENT, this.onCrashReportAck);
38
-
39
- // 顺序执行:先处理待上报崩溃,再清理旧文件(避免竞态)
40
- this.processPendingCrashes().then(() => this.cleanupOldCrashes()).catch(e => {
41
- _rumCore.logger.error('crash-collector', 'Failed to process crash reports', e);
42
- });
43
- }
44
- async processPendingCrashes() {
45
- if (this.initialized) return;
46
- this.initialized = true;
47
-
48
- // 不以 crashedLastRun 作为 gating:KSCrash 的 crashedLastLaunch 只在崩溃后的下一次
49
- // 启动为 true,再下一次正常启动就清零,会导致遗留崩溃文件永远不被处理。
50
- // 只要磁盘上有 pending 文件就读取并上报。
51
- await (0, _crash2.safeNativeCall)(() => _NativeRumSdkReactnative.default.crashedLastRun(), 'crashedLastRun');
52
- const crashesJson = await (0, _crash2.safeNativeCall)(() => _NativeRumSdkReactnative.default.getPendingCrashes(), 'getPendingCrashes');
53
- if (!crashesJson) return;
54
- let crashes = [];
55
- try {
56
- crashes = JSON.parse(crashesJson) || [];
57
- } catch (e) {
58
- _rumCore.logger.error('crash-collector', 'Failed to parse crashes JSON', e);
59
- // 清理损坏的文件,避免每次启动重复尝试解析
60
- await (0, _crash2.safeNativeCall)(() => _NativeRumSdkReactnative.default.clearAllCrashReports(), 'clearAllCrashReports');
61
- return;
62
- }
63
- if (!Array.isArray(crashes) || crashes.length === 0) return;
64
- const crashIds = [];
65
- const hasFilesWithoutId = crashes.some(c => !c.crash_id);
66
- for (let i = 0; i < crashes.length; i++) {
67
- try {
68
- const event = (0, _crash2.convertCrashToRumEvent)(crashes[i]);
69
- const cid = crashes[i].crash_id;
70
- // 将瞬态 crash_id 注入 snapshots(而非根级),reporter 上报前从中提取
71
- // 用于 ack,并在序列化前从 snapshots 中剥离,不会发送到后端
72
- if (cid) {
73
- try {
74
- const snapshotsObj = JSON.parse(event.snapshots);
75
- snapshotsObj[_crash.CRASH_ID_FIELD] = cid;
76
- event.snapshots = JSON.stringify(snapshotsObj);
77
- } catch {
78
- // snapshots 解析失败时降级:仍挂载到根级以保证 ack 机制可用
79
- event[_crash.CRASH_ID_FIELD] = cid;
80
- }
81
- this.pendingAckIds.add(cid);
82
- crashIds.push(cid);
83
- }
84
- // 最后一个崩溃事件使用 immediate 立即 flush 整个队列
85
- const isLast = i === crashes.length - 1;
86
- this.sendEvent?.(event, isLast ? {
87
- immediate: true
88
- } : undefined);
89
- } catch (e) {
90
- _rumCore.logger.warn('crash-collector', 'Failed to convert crash', e);
91
- }
92
- }
93
-
94
- // 有 crash_id 的文件:不在此处删除,等 reporter 上报成功后由 onCrashReportAck
95
- // 删除;上报失败则保留,下次启动重试(cleanupOldCrashes 兜底防止无限增长)。
96
-
97
- // 无 crash_id 的旧版 NDK 崩溃文件无法 ack:仅当本次没有待 ack 的带 id 文件时
98
- // 才安全清理(clearAllCrashReports 会删除全部文件,避免误删 ack-pending)。
99
- if (hasFilesWithoutId && crashIds.length === 0) {
100
- await (0, _crash2.safeNativeCall)(() => _NativeRumSdkReactnative.default.clearAllCrashReports(), 'clearAllCrashReports');
101
- }
102
- }
103
-
104
- /** 兜底清理:删除过期和超限的崩溃文件,防止存储耗尽 */
105
- async cleanupOldCrashes() {
106
- // 最多保留 10 个崩溃文件,7 天过期
107
- const MAX_FILES = 10;
108
- const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
109
- await (0, _crash2.safeNativeCall)(() => _NativeRumSdkReactnative.default.cleanupOldCrashes(MAX_FILES, MAX_AGE_MS), 'cleanupOldCrashes');
110
- }
111
- onCrashContextChange = context => {
112
- try {
113
- _NativeRumSdkReactnative.default.updateCrashContext(JSON.stringify(context));
114
- } catch {
115
- //
116
- }
117
- };
118
-
119
- /**
120
- * reporter 上报确认回调:
121
- * - 成功:删除对应 crash_id 的崩溃文件(确保上报成功后才删,不丢数据);
122
- * - 失败:保留文件,下次启动重试,仅从内存待确认集合中移除。
123
- */
124
- onCrashReportAck = ack => {
125
- if (!ack || !Array.isArray(ack.crashIds) || ack.crashIds.length === 0) {
126
- return;
127
- }
128
- // 只处理本采集器登记过的 id
129
- const toClear = ack.crashIds.filter(id => this.pendingAckIds.has(id));
130
- if (toClear.length === 0) return;
131
- if (!ack.success) {
132
- // 上报失败:文件保留,从待确认集合移除(下次启动重新登记与上报)
133
- for (const id of toClear) this.pendingAckIds.delete(id);
134
- return;
135
- }
136
- (0, _crash2.safeNativeCall)(() => _NativeRumSdkReactnative.default.clearCrashReports(JSON.stringify(toClear)), 'clearCrashReports').finally(() => {
137
- for (const id of toClear) this.pendingAckIds.delete(id);
138
- });
139
- };
140
- destroy() {
141
- this.ctx?.emitter.off(_crash.CRASH_CONTEXT_EVENT, this.onCrashContextChange);
142
- this.ctx?.emitter.off(_crash.CRASH_REPORT_ACK_EVENT, this.onCrashReportAck);
143
- this.pendingAckIds.clear();
144
- this.sendEvent = undefined;
145
- }
146
- }
147
- exports.default = CrashCollector;
148
- //# sourceMappingURL=crash.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var _rumCore=require("@arms/rum-core"),_NativeRumSdkReactnative=_interopRequireDefault(require("../../NativeRumSdkReactnative")),_crash=require("../../types/crash"),_crash2=require("../../utils/crash");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}class CrashCollector{name="crash-collector";initialized=!1;pendingAckIds=new Set;setup(e,t){const{collectors:a={}}=e.getConfig();!1!==a.crash&&(this.ctx=e,this.sendEvent=t,e.emitter.on(_crash.CRASH_CONTEXT_EVENT,this.onCrashContextChange),e.emitter.on(_crash.CRASH_REPORT_ACK_EVENT,this.onCrashReportAck),this.processPendingCrashes().then(()=>this.cleanupOldCrashes()).catch(e=>{_rumCore.logger.error("crash-collector","Failed to process crash reports",e)}))}async processPendingCrashes(){if(this.initialized)return;this.initialized=!0,await(0,_crash2.safeNativeCall)(()=>_NativeRumSdkReactnative.default.crashedLastRun(),"crashedLastRun");const e=await(0,_crash2.safeNativeCall)(()=>_NativeRumSdkReactnative.default.getPendingCrashes(),"getPendingCrashes");if(!e)return;let t=[];try{t=JSON.parse(e)||[]}catch(e){return _rumCore.logger.error("crash-collector","Failed to parse crashes JSON",e),void await(0,_crash2.safeNativeCall)(()=>_NativeRumSdkReactnative.default.clearAllCrashReports(),"clearAllCrashReports")}if(!Array.isArray(t)||0===t.length)return;const a=[],r=t.some(e=>!e.crash_id);for(let e=0;e<t.length;e++)try{const r=(0,_crash2.convertCrashToRumEvent)(t[e]),s=t[e].crash_id;if(s){try{const e=JSON.parse(r.snapshots);e[_crash.CRASH_ID_FIELD]=s,r.snapshots=JSON.stringify(e)}catch{r[_crash.CRASH_ID_FIELD]=s}this.pendingAckIds.add(s),a.push(s)}const i=e===t.length-1;this.sendEvent?.(r,i?{immediate:!0}:void 0)}catch(e){_rumCore.logger.warn("crash-collector","Failed to convert crash",e)}r&&0===a.length&&await(0,_crash2.safeNativeCall)(()=>_NativeRumSdkReactnative.default.clearAllCrashReports(),"clearAllCrashReports")}async cleanupOldCrashes(){await(0,_crash2.safeNativeCall)(()=>_NativeRumSdkReactnative.default.cleanupOldCrashes(10,6048e5),"cleanupOldCrashes")}onCrashContextChange=e=>{try{_NativeRumSdkReactnative.default.updateCrashContext(JSON.stringify(e))}catch{}};onCrashReportAck=e=>{if(!e||!Array.isArray(e.crashIds)||0===e.crashIds.length)return;const t=e.crashIds.filter(e=>this.pendingAckIds.has(e));if(0!==t.length)if(e.success)(0,_crash2.safeNativeCall)(()=>_NativeRumSdkReactnative.default.clearCrashReports(JSON.stringify(t)),"clearCrashReports").finally(()=>{for(const e of t)this.pendingAckIds.delete(e)});else for(const e of t)this.pendingAckIds.delete(e)};destroy(){this.ctx?.emitter.off(_crash.CRASH_CONTEXT_EVENT,this.onCrashContextChange),this.ctx?.emitter.off(_crash.CRASH_REPORT_ACK_EVENT,this.onCrashReportAck),this.pendingAckIds.clear(),this.sendEvent=void 0}}exports.default=CrashCollector;