@arms/rum-reactnative 0.1.3-beta.5 → 0.1.4-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +9 -2
  2. package/android/build.gradle +12 -1
  3. package/android/gradle.properties +3 -1
  4. package/android/src/main/AndroidManifest.xml +4 -1
  5. package/android/src/main/java/com/rumsdkreactnative/RumSdkReactnativeModule.kt +67 -14
  6. package/android/src/main/java/com/rumsdkreactnative/RumSdkReactnativePackage.kt +9 -2
  7. package/docs/ReactNative SDK/351/205/215/347/275/256/345/217/202/350/200/203.md" +25 -0
  8. package/docs/SDK /347/211/210/346/234/254/350/257/264/346/230/216.md" +20 -0
  9. package/docs//346/216/245/345/205/245ReactNative/345/272/224/347/224/250.md +2 -1
  10. package/ios/RumAppStateRecorder.h +69 -0
  11. package/ios/RumAppStateRecorder.mm +547 -0
  12. package/ios/RumCrashReporter.h +32 -0
  13. package/ios/RumCrashReporter.mm +97 -11
  14. package/ios/RumSdkReactnative.h +11 -1
  15. package/ios/RumSdkReactnative.mm +115 -29
  16. package/lib/commonjs/NativeRumSdkReactnative.js +1 -54
  17. package/lib/commonjs/collector/action/index.js +1 -108
  18. package/lib/commonjs/collector/exception/crash.js +1 -148
  19. package/lib/commonjs/collector/exception/index.js +1 -238
  20. package/lib/commonjs/collector/exception/oom.js +1 -0
  21. package/lib/commonjs/collector/index.js +1 -42
  22. package/lib/commonjs/collector/resource/api.js +1 -292
  23. package/lib/commonjs/collector/view/perf.js +1 -79
  24. package/lib/commonjs/collector/view/pv.js +1 -88
  25. package/lib/commonjs/expoConfigPlugin.js +1 -37
  26. package/lib/commonjs/index.js +1 -29
  27. package/lib/commonjs/plugin.js +1 -47
  28. package/lib/commonjs/processor/default-processor.js +1 -27
  29. package/lib/commonjs/processor/session-processor.js +1 -47
  30. package/lib/commonjs/reporter/index.js +1 -148
  31. package/lib/commonjs/reporter/monitor-reporter.js +1 -43
  32. package/lib/commonjs/shell.js +1 -91
  33. package/lib/commonjs/types/client.js +1 -6
  34. package/lib/commonjs/types/crash.js +1 -37
  35. package/lib/commonjs/types/index.js +1 -2
  36. package/lib/commonjs/types/oom.js +1 -0
  37. package/lib/commonjs/utils/action.js +1 -305
  38. package/lib/commonjs/utils/api.js +1 -84
  39. package/lib/commonjs/utils/base.js +1 -95
  40. package/lib/commonjs/utils/crash.js +1 -149
  41. package/lib/commonjs/utils/platform.js +1 -162
  42. package/lib/commonjs/utils/session.js +1 -158
  43. package/lib/commonjs/utils/url.js +1 -59
  44. package/lib/commonjs/utils/view.js +1 -93
  45. package/lib/module/NativeRumSdkReactnative.js +1 -51
  46. package/lib/module/collector/action/index.js +1 -103
  47. package/lib/module/collector/exception/crash.js +1 -143
  48. package/lib/module/collector/exception/index.js +1 -233
  49. package/lib/module/collector/exception/oom.js +1 -0
  50. package/lib/module/collector/index.js +1 -8
  51. package/lib/module/collector/resource/api.js +1 -287
  52. package/lib/module/collector/view/perf.js +1 -79
  53. package/lib/module/collector/view/pv.js +1 -83
  54. package/lib/module/expoConfigPlugin.js +1 -33
  55. package/lib/module/index.js +1 -7
  56. package/lib/module/plugin.js +1 -42
  57. package/lib/module/processor/default-processor.js +1 -22
  58. package/lib/module/processor/session-processor.js +1 -42
  59. package/lib/module/reporter/index.js +1 -144
  60. package/lib/module/reporter/monitor-reporter.js +1 -38
  61. package/lib/module/shell.js +1 -84
  62. package/lib/module/types/client.js +1 -4
  63. package/lib/module/types/crash.js +1 -33
  64. package/lib/module/types/index.js +1 -2
  65. package/lib/module/types/oom.js +1 -0
  66. package/lib/module/utils/action.js +1 -299
  67. package/lib/module/utils/api.js +1 -76
  68. package/lib/module/utils/base.js +1 -90
  69. package/lib/module/utils/crash.js +1 -144
  70. package/lib/module/utils/platform.js +1 -150
  71. package/lib/module/utils/session.js +1 -154
  72. package/lib/module/utils/url.js +1 -54
  73. package/lib/module/utils/view.js +1 -88
  74. package/lib/typescript/commonjs/src/NativeRumSdkReactnative.d.ts +10 -0
  75. package/lib/typescript/commonjs/src/collector/exception/oom.d.ts +34 -0
  76. package/lib/typescript/commonjs/src/collector/index.d.ts +1 -0
  77. package/lib/typescript/commonjs/src/shell.d.ts +1 -1
  78. package/lib/typescript/commonjs/src/types/client.d.ts +27 -0
  79. package/lib/typescript/commonjs/src/types/oom.d.ts +58 -0
  80. package/lib/typescript/commonjs/src/utils/crash.d.ts +13 -0
  81. package/lib/typescript/module/src/NativeRumSdkReactnative.d.ts +10 -0
  82. package/lib/typescript/module/src/collector/exception/oom.d.ts +34 -0
  83. package/lib/typescript/module/src/collector/index.d.ts +1 -0
  84. package/lib/typescript/module/src/shell.d.ts +1 -1
  85. package/lib/typescript/module/src/types/client.d.ts +27 -0
  86. package/lib/typescript/module/src/types/oom.d.ts +58 -0
  87. package/lib/typescript/module/src/utils/crash.d.ts +13 -0
  88. package/package.json +1 -2
  89. package/src/NativeRumSdkReactnative.ts +18 -0
@@ -1,233 +1 @@
1
- "use strict";
2
-
3
- import { isArray, interceptFunction, matchList, RumEventType } from '@arms/rum-core';
4
- export default class ExceptionCollector {
5
- name = 'exception-collector';
6
- installed = false;
7
- /** fatal 去重保护:防止连续 fatal error 重复上报 */
8
- handlingFatal = false;
9
- /** 防止 errorHandle 内部 console.error 形成循环 */
10
- inErrorHandle = false;
11
- origin = {
12
- consoleError: console.error,
13
- originalOnError: null,
14
- originalOnUnhandledRejection: null
15
- };
16
- setup(ctx, sendEvent) {
17
- this.ctx = ctx;
18
- this.sendEvent = sendEvent;
19
- this.hackOrigin();
20
- }
21
- hackOrigin() {
22
- // 防止重复安装覆盖 originalOnError
23
- if (this.installed) return;
24
- this.installed = true;
25
- const self = this;
26
- const {
27
- collectors = {}
28
- } = this.ctx.getConfig();
29
- if (collectors.jsError !== false) {
30
- if (ErrorUtils) {
31
- this.origin.originalOnError = ErrorUtils.getGlobalHandler?.();
32
- ErrorUtils.setGlobalHandler(this.onError);
33
- }
34
- // Promise rejection: 优先 Hermes,JSC 兜底
35
- if (HermesInternal?.enablePromiseRejectionTracker && HermesInternal?.hasPromise?.()) {
36
- HermesInternal.enablePromiseRejectionTracker({
37
- allRejections: true,
38
- onUnhandled: this.onUnhandled
39
- });
40
- } else {
41
- // JSC / 其他引擎:监听 unhandledrejection 事件
42
- this.setupJSCPromiseRejection();
43
- }
44
- }
45
- if (collectors.consoleError !== false) {
46
- interceptFunction(console, 'error', function (error) {
47
- if (self.inErrorHandle) return; // 防止循环
48
- self.errorHandle(error, 'console.error');
49
- });
50
- }
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
- }
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 不可用时直接链式调用
101
- if (this.origin.originalOnError) {
102
- this.origin.originalOnError(e, isFatal);
103
- }
104
- if (!isFatal) {
105
- this.handlingFatal = false;
106
- }
107
- };
108
- onUnhandled = (_id, error) => {
109
- this.errorHandle(error, 'onUnhandled');
110
- };
111
- errorHandle = (error, source = '') => {
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);
147
- }
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;
170
- }
171
- };
172
-
173
- /**
174
- * 根据stack获取Error信息
175
- * TODO: 增加基于Stack解析filename和lineNumber的能力
176
- * @param stack
177
- * @returns
178
- */
179
- getErrorByStack(stack = '') {
180
- const res = stack.split('\n');
181
- const prefix = 'at ';
182
- if (res.length < 2) return;
183
- for (let i = 1; i < res.length; i++) {
184
- const str = res[i] || '';
185
- if (str.trim().startsWith(prefix)) {
186
- const line = res[i - 1] || '';
187
- const index = line.indexOf(': ');
188
- return {
189
- name: line.substring(0, index),
190
- message: line.substring(index + 2)
191
- };
192
- }
193
- }
194
- return;
195
- }
196
- errorFilter(err) {
197
- let {
198
- exception: filters
199
- } = this.ctx.getConfig().filters || {};
200
- if (!filters) {
201
- return false;
202
- }
203
- if (!isArray(filters)) {
204
- filters = [filters];
205
- }
206
- return matchList(filters, err.name, true, err) || matchList(filters, err.message, true, err) || matchList(filters, err.stack || '', true, err);
207
- }
208
- destroy() {
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
- }
229
- }
230
- this.installed = false;
231
- }
232
- }
233
- //# sourceMappingURL=index.js.map
1
+ "use strict";import{isArray,interceptFunction,matchList,RumEventType}from"@arms/rum-core";export default class ExceptionCollector{name="exception-collector";installed=!1;handlingFatal=!1;inErrorHandle=!1;origin={consoleError:console.error,originalOnError:null,originalOnUnhandledRejection:null};setup(r,n){this.ctx=r,this.sendEvent=n,this.hackOrigin()}hackOrigin(){if(this.installed)return;this.installed=!0;const r=this,{collectors:n={}}=this.ctx.getConfig();!1!==n.jsError&&(ErrorUtils&&(this.origin.originalOnError=ErrorUtils.getGlobalHandler?.(),ErrorUtils.setGlobalHandler(this.onError)),HermesInternal?.enablePromiseRejectionTracker&&HermesInternal?.hasPromise?.()?HermesInternal.enablePromiseRejectionTracker({allRejections:!0,onUnhandled:this.onUnhandled}):this.setupJSCPromiseRejection()),!1!==n.consoleError&&interceptFunction(console,"error",function(n){r.inErrorHandle||r.errorHandle(n,"console.error")})}setupJSCPromiseRejection(){try{if("undefined"!=typeof globalThis){const r=this;this.origin.originalOnUnhandledRejection=globalThis.onunhandledrejection||null,globalThis.onunhandledrejection=function(n){n?.reason&&r.errorHandle(n.reason,"onUnhandled"),"function"==typeof r.origin.originalOnUnhandledRejection&&r.origin.originalOnUnhandledRejection.call(this,n)}}}catch{}}onError=(r,n)=>{if(n){if(this.handlingFatal)return;this.handlingFatal=!0}if(this.errorHandle(r,"onError"),n&&this.ctx)try{const e=this.ctx.reporter;if(e&&"function"==typeof e.flush)return void Promise.race([e.flush(),new Promise(r=>setTimeout(r,2e3))]).finally(()=>{this.origin.originalOnError&&this.origin.originalOnError(r,n)})}catch{}this.origin.originalOnError&&this.origin.originalOnError(r,n),n||(this.handlingFatal=!1)};onUnhandled=(r,n)=>{this.errorHandle(n,"onUnhandled")};errorHandle=(r,n="")=>{if(!this.inErrorHandle&&null!=r){this.inErrorHandle=!0;try{let e="",i="",t="",o="",s=0,l=0;if(r instanceof Object)e=String(r.name||r.constructor?.name||"Error"),i=String(r.message||""),t=String(r.filename||r.fileName||""),o=String(r.stack||""),!o&&r.componentStack&&(o=String(r.componentStack)),s=Number(r.lineno||r.lineNumber||0),l=Number(r.colno||r.columnNumber||0);else if("string"==typeof r){o=r;const n=this.getErrorByStack(r);n?(e=n.name||"Error",i=n.message||""):(e="Error",i=r.substring(0,200))}else"number"!=typeof r&&"boolean"!=typeof r||(e="Error",i=String(r));if(!e||!i&&!o||this.errorFilter(r))return;if(this.ctx&&this.sendEvent){const r=this.ctx.session.getBaseEvent(),a="console.error"===n?"console":"event";this.sendEvent({...r,event_type:RumEventType.EXCEPTION,source:a,type:"error",name:e,message:i,file:t,stack:o,line:s,column:l,times:1})}}finally{this.inErrorHandle=!1}}};getErrorByStack(r=""){const n=r.split("\n");if(!(n.length<2))for(let r=1;r<n.length;r++){if((n[r]||"").trim().startsWith("at ")){const e=n[r-1]||"",i=e.indexOf(": ");return{name:e.substring(0,i),message:e.substring(i+2)}}}}errorFilter(r){let{exception:n}=this.ctx.getConfig().filters||{};return!!n&&(isArray(n)||(n=[n]),matchList(n,r.name,!0,r)||matchList(n,r.message,!0,r)||matchList(n,r.stack||"",!0,r))}destroy(){if(this.installed){if(this.origin.consoleError&&(console.error=this.origin.consoleError),ErrorUtils&&this.origin.originalOnError)try{ErrorUtils.setGlobalHandler(this.origin.originalOnError)}catch{}if("undefined"!=typeof globalThis&&null!==this.origin.originalOnUnhandledRejection)try{globalThis.onunhandledrejection=this.origin.originalOnUnhandledRejection}catch{}this.installed=!1}}}
@@ -0,0 +1 @@
1
+ "use strict";import{logger}from"@arms/rum-core";import NativeRumSdkReactnative from"../../NativeRumSdkReactnative.js";import{CRASH_CONTEXT_EVENT,CRASH_ID_FIELD,CRASH_REPORT_ACK_EVENT}from"../../types/crash.js";import{convertOomToRumEvent,safeNativeCall}from"../../utils/crash.js";export default class OomCollector{name="oom-collector";initialized=!1;pendingAckIds=new Set;setup(t,e){const{collectors:o={}}=t.getConfig();if(!0===o.oom){this.ctx=t,this.sendEvent=e;try{NativeRumSdkReactnative.updateCollectorsConfig(JSON.stringify({oom:!0}))}catch{}t.emitter.on(CRASH_CONTEXT_EVENT,this.onCrashContextChange),t.emitter.on(CRASH_REPORT_ACK_EVENT,this.onCrashReportAck),this.processPendingOomReports().catch(t=>{logger.error("oom-collector","Failed to process OOM reports",t)})}}async processPendingOomReports(){if(this.initialized)return;this.initialized=!0;const t=await safeNativeCall(()=>NativeRumSdkReactnative.getPendingOomReports(),"getPendingOomReports");if(!t)return;let e=[];try{e=JSON.parse(t)||[]}catch(t){return void logger.error("oom-collector","Failed to parse OOM reports JSON",t)}if(Array.isArray(e)&&0!==e.length)for(const t of e)try{const e=convertOomToRumEvent(t),o=t.oom_id;if(o){try{const t=JSON.parse(e.snapshots);t[CRASH_ID_FIELD]=o,e.snapshots=JSON.stringify(t)}catch{e[CRASH_ID_FIELD]=o}this.pendingAckIds.add(o)}this.sendEvent?.(e,{immediate:!0})}catch(t){logger.warn("oom-collector","Failed to convert OOM report",t)}}onCrashContextChange=t=>{try{NativeRumSdkReactnative.updateCrashContext(JSON.stringify(t))}catch{}};onCrashReportAck=t=>{if(!t||!Array.isArray(t.crashIds)||0===t.crashIds.length)return;const e=t.crashIds.filter(t=>this.pendingAckIds.has(t));if(0!==e.length)if(t.success)safeNativeCall(()=>NativeRumSdkReactnative.clearOomReports(JSON.stringify(e)),"clearOomReports").finally(()=>{for(const t of e)this.pendingAckIds.delete(t)});else for(const t of e)this.pendingAckIds.delete(t)};destroy(){this.ctx?.emitter.off(CRASH_CONTEXT_EVENT,this.onCrashContextChange),this.ctx?.emitter.off(CRASH_REPORT_ACK_EVENT,this.onCrashReportAck),this.pendingAckIds.clear(),this.sendEvent=void 0}}
@@ -1,8 +1 @@
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
+ "use strict";export{default as PvCollector}from"./view/pv.js";export{default as ExceptionCollector}from"./exception/index.js";export{default as CrashCollector}from"./exception/crash.js";export{default as OomCollector}from"./exception/oom.js";export{default as ApiCollector}from"./resource/api.js";export{default as ActionCollector}from"./action/index.js";
@@ -1,287 +1 @@
1
- "use strict";
2
-
3
- import { find, generateGUID, generateSpanId, generateTraceId, isString, interceptFunction, restoreFunction, isFunction, makeTracingHeaders, matchList, parseTracingOptions, performDraw, RumEventType, ResourceStatus, urlMatch, logger } from '@arms/rum-core';
4
- import { getCurrentTime } from "../../utils/base.js";
5
- import { ApiType, reviseApiAttr, getResourceName, getResourceSuccessByCode } from "../../utils/api.js";
6
- export default class ApiCollector {
7
- name = 'api-collector';
8
- setup(ctx, sendEvent) {
9
- const collectors = ctx.getConfig().collectors || {};
10
- if (collectors.api === false) {
11
- return;
12
- }
13
- this.sendEvent = sendEvent;
14
- this.ctx = ctx;
15
- // this.perfObserver();
16
- this.hackXhr();
17
- // this.hackFetch();
18
- }
19
- injectTracing(apiAttr, inject) {
20
- const {
21
- tracing = false,
22
- pid,
23
- version = '1.0.0'
24
- } = this.ctx.getConfig();
25
- const {
26
- enable,
27
- sample,
28
- propagatorTypes,
29
- allowedUrls,
30
- tracestate,
31
- baggage = false
32
- } = parseTracingOptions(tracing);
33
- if (!enable) return;
34
- // @ts-ignore
35
- const resolvedTraceOption = find(allowedUrls, item => matchList([item.match], apiAttr.url || ''));
36
- if (!resolvedTraceOption) return;
37
- let pTypes = resolvedTraceOption.propagatorTypes;
38
- if (pTypes && pTypes.length === 0) {
39
- pTypes = propagatorTypes;
40
- }
41
- // skywalking
42
- let isSw8 = false;
43
- if (pTypes && pTypes.indexOf('sw8') > -1) {
44
- pTypes = ['sw8'];
45
- isSw8 = true;
46
- }
47
- const session = this.ctx.session;
48
- const traceId = isSw8 ? generateGUID() : generateTraceId();
49
- const spanId = isSw8 ? generateGUID() : generateSpanId();
50
- const sampled = performDraw(sample);
51
- const uid = session.getUserId();
52
- const sid = session.getSessionId();
53
- const traceContext = tracestate ? `rum=v2&browser&${pid}&${sid}&${uid}` : undefined;
54
- const baggageContext = baggage ? `rum=v2,appType=browser,pid=${pid},sid=${sid},uid=${uid}` : undefined;
55
- const tracingHeaders = makeTracingHeaders(traceId, spanId, sampled, pTypes || [], {
56
- tracestate: traceContext,
57
- baggage: baggageContext,
58
- appId: pid,
59
- appVersion: version,
60
- viewName: apiAttr.view?.name
61
- // host: location.host
62
- });
63
- if (sampled) {
64
- apiAttr.trace_id = traceId;
65
- apiAttr.trace_data = JSON.stringify({
66
- spanId,
67
- sample,
68
- sampled,
69
- headers: tracingHeaders
70
- });
71
- }
72
- inject(tracingHeaders);
73
- }
74
- hackXhr() {
75
- const oldXHRProto = XMLHttpRequest.prototype;
76
- const that = this;
77
- interceptFunction(oldXHRProto, 'open', function (method, url, async, user, password) {
78
- const filters = that.ctx.getConfig().filters || {};
79
- const urlStr = String(url);
80
- const methodStr = method.toUpperCase();
81
- // chrome 对 URL 的最大长度为 8182 个字符
82
- if (urlMatch(urlStr, filters.resource) || methodStr === 'HEAD' || urlStr.length > 8182) return;
83
- this.apiCache = {
84
- type: ApiType.XHR,
85
- method,
86
- url,
87
- async,
88
- user,
89
- password,
90
- headers: {}
91
- };
92
- const baseRumEvent = that.ctx.session.getBaseEvent();
93
- this.apiAttr = {
94
- ...baseRumEvent,
95
- url: urlStr,
96
- method: methodStr
97
- };
98
- }, true);
99
- interceptFunction(oldXHRProto, 'setRequestHeader', function (header, value) {
100
- if (!this.apiCache) return;
101
- this.apiCache.headers = {
102
- ...this.apiCache.headers,
103
- [header]: value
104
- };
105
- }, true);
106
- interceptFunction(oldXHRProto, 'send', function (body) {
107
- if (!this.apiAttr || !this.apiCache) return;
108
- let apiAttr = this.apiAttr;
109
- this.apiCache.data = body;
110
- that.injectTracing(this.apiAttr, tracingHeaders => {
111
- Object.keys(tracingHeaders).forEach(key => {
112
- tracingHeaders[key] && this.setRequestHeader(key, tracingHeaders[key]);
113
- });
114
- });
115
- this.addEventListener('loadend', async () => {
116
- const {
117
- status,
118
- statusText,
119
- response
120
- } = this;
121
- const {
122
- evaluateApi
123
- } = that.ctx.getConfig();
124
- if (!this.apiAttr) return;
125
- apiAttr.status_code = status;
126
- apiAttr.message = statusText;
127
- const entry = {
128
- name: this.apiAttr.url,
129
- entryType: ApiType.XHR,
130
- duration: getCurrentTime() - apiAttr.timestamp
131
- };
132
- if (isFunction(evaluateApi)) {
133
- try {
134
- let error;
135
- if (apiAttr.success === ResourceStatus.Failed) {
136
- error = new Error(ApiType.XHR + ' error');
137
- }
138
- const attrs = await evaluateApi?.(this.apiCache, response, error, this);
139
- this.apiAttr = apiAttr = {
140
- ...apiAttr,
141
- ...reviseApiAttr(attrs)
142
- };
143
- } catch (e) {
144
- logger.warn('api-collector', 'evaluateApi failed for XHR', e);
145
- }
146
- }
147
- that.sendAPI(apiAttr, entry);
148
- });
149
- this.addEventListener('error', () => {
150
- this.apiAttr && (this.apiAttr.success = ResourceStatus.Failed);
151
- });
152
- this.addEventListener('abort', () => {
153
- if (this.apiAttr) {
154
- this.apiAttr.success = ResourceStatus.Failed;
155
- this.apiAttr.message = 'abort';
156
- }
157
- });
158
- }, true);
159
- }
160
- hackFetch() {
161
- const that = this;
162
- const originFetch = this.originFetch = globalThis.fetch;
163
- const filters = this.ctx.getConfig().filters || {};
164
- globalThis.fetch = function (input, init = {}) {
165
- const isReq = input instanceof Request;
166
- const urlStr = isReq ? input.url : isString(input) ? input : input.toString();
167
- let method = init.method;
168
- let headers = init.headers;
169
- if (isReq) {
170
- !method && (method = input.method);
171
- !headers && (headers = input.headers);
172
- }
173
- method = method ? method.toUpperCase() : 'GET';
174
- if (!headers) {
175
- init.headers = headers = new Headers();
176
- }
177
- if (urlMatch(urlStr, filters.resource) || method === 'HEAD' || init?.mode === 'no-cors' || urlStr.length > 8182) {
178
- return originFetch.call(global, input, init);
179
- }
180
- const baseRumEvent = that.ctx.session.getBaseEvent();
181
- let apiAttr = {
182
- ...baseRumEvent,
183
- url: urlStr,
184
- type: ApiType.FETCH,
185
- method
186
- };
187
- let apiResp;
188
- let apiError;
189
- that.injectTracing(apiAttr, tracingHeaders => {
190
- Object.keys(tracingHeaders).forEach(key => {
191
- if (!headers) return;
192
- if (headers instanceof Headers) {
193
- tracingHeaders[key] && headers.set(key, tracingHeaders[key]);
194
- } else {
195
- // @ts-ignore
196
- headers[key] = tracingHeaders[key];
197
- }
198
- });
199
- });
200
- async function fetchFinally() {
201
- const {
202
- evaluateApi
203
- } = that.ctx.getConfig();
204
- const entry = {
205
- name: apiAttr.url,
206
- entryType: ApiType.FETCH,
207
- duration: getCurrentTime() - apiAttr.timestamp
208
- };
209
- if (isFunction(evaluateApi)) {
210
- try {
211
- const attrs = await evaluateApi?.({
212
- url: input,
213
- ...init
214
- }, apiResp, apiError);
215
- apiAttr = {
216
- ...apiAttr,
217
- ...reviseApiAttr(attrs)
218
- };
219
- } catch (e) {
220
- logger.warn('api-collector', 'evaluateApi failed for fetch', e);
221
- }
222
- }
223
- that.sendAPI(apiAttr, entry);
224
- }
225
- return originFetch.call(globalThis, input, init).then(response => {
226
- // @ts-ignore
227
- if (response || isFunction(response.clone)) {
228
- apiResp = response.clone();
229
- const {
230
- status,
231
- statusText
232
- } = apiResp;
233
- apiAttr.status_code = status;
234
- apiAttr.message = statusText;
235
- fetchFinally().then();
236
- }
237
- return response;
238
- }).catch(error => {
239
- apiError = error;
240
- apiAttr.message = String(error);
241
- apiAttr.success = ResourceStatus.Failed;
242
- fetchFinally().then();
243
- throw error;
244
- });
245
- };
246
- }
247
- sendAPI(apiAttr, entry) {
248
- const {
249
- parseResourceName
250
- } = this.ctx.getConfig();
251
- const {
252
- url,
253
- status_code,
254
- view
255
- } = apiAttr;
256
- const success = getResourceSuccessByCode(status_code);
257
- const measures = {
258
- duration: entry.duration
259
- };
260
- const event = {
261
- event_type: RumEventType.RESOURCE,
262
- url,
263
- name: getResourceName(url || '', parseResourceName),
264
- success,
265
- type: entry.initiatorType || entry.entryType,
266
- ...apiAttr,
267
- timing_data: JSON.stringify(entry),
268
- // Measure
269
- ...measures,
270
- view
271
- };
272
- this.sendEvent?.(event);
273
- }
274
- destroy() {
275
- this.sendEvent = () => {};
276
- // 恢复XHR
277
- const oldXHRProto = XMLHttpRequest.prototype;
278
- restoreFunction(oldXHRProto, 'open');
279
- restoreFunction(oldXHRProto, 'setRequestHeader');
280
- restoreFunction(oldXHRProto, 'send');
281
- // 恢复 fetch
282
- if (this.originFetch) {
283
- globalThis.fetch = this.originFetch;
284
- }
285
- }
286
- }
287
- //# sourceMappingURL=api.js.map
1
+ "use strict";import{find,generateGUID,generateSpanId,generateTraceId,isString,interceptFunction,restoreFunction,isFunction,makeTracingHeaders,matchList,parseTracingOptions,performDraw,RumEventType,ResourceStatus,urlMatch,logger}from"@arms/rum-core";import{getCurrentTime}from"../../utils/base.js";import{ApiType,reviseApiAttr,getResourceName,getResourceSuccessByCode}from"../../utils/api.js";export default class ApiCollector{name="api-collector";setup(e,t){!1!==(e.getConfig().collectors||{}).api&&(this.sendEvent=t,this.ctx=e,this.hackXhr())}injectTracing(e,t){const{tracing:s=!1,pid:i,version:r="1.0.0"}=this.ctx.getConfig(),{enable:a,sample:n,propagatorTypes:c,allowedUrls:o,tracestate:p,baggage:u=!1}=parseTracingOptions(s);if(!a)return;const h=find(o,t=>matchList([t.match],e.url||""));if(!h)return;let d=h.propagatorTypes;d&&0===d.length&&(d=c);let g=!1;d&&d.indexOf("sw8")>-1&&(d=["sw8"],g=!0);const l=this.ctx.session,m=g?generateGUID():generateTraceId(),f=g?generateGUID():generateSpanId(),y=performDraw(n),T=l.getUserId(),A=l.getSessionId(),v=makeTracingHeaders(m,f,y,d||[],{tracestate:p?`rum=v2&browser&${i}&${A}&${T}`:void 0,baggage:u?`rum=v2,appType=browser,pid=${i},sid=${A},uid=${T}`:void 0,appId:i,appVersion:r,viewName:e.view?.name});y&&(e.trace_id=m,e.trace_data=JSON.stringify({spanId:f,sample:n,sampled:y,headers:v})),t(v)}hackXhr(){const e=XMLHttpRequest.prototype,t=this;interceptFunction(e,"open",function(e,s,i,r,a){const n=t.ctx.getConfig().filters||{},c=String(s),o=e.toUpperCase();if(urlMatch(c,n.resource)||"HEAD"===o||c.length>8182)return;this.apiCache={type:ApiType.XHR,method:e,url:s,async:i,user:r,password:a,headers:{}};const p=t.ctx.session.getBaseEvent();this.apiAttr={...p,url:c,method:o}},!0),interceptFunction(e,"setRequestHeader",function(e,t){this.apiCache&&(this.apiCache.headers={...this.apiCache.headers,[e]:t})},!0),interceptFunction(e,"send",function(e){if(!this.apiAttr||!this.apiCache)return;let s=this.apiAttr;this.apiCache.data=e,t.injectTracing(this.apiAttr,e=>{Object.keys(e).forEach(t=>{e[t]&&this.setRequestHeader(t,e[t])})}),this.addEventListener("loadend",async()=>{const{status:e,statusText:i,response:r}=this,{evaluateApi:a}=t.ctx.getConfig();if(!this.apiAttr)return;s.status_code=e,s.message=i;const n={name:this.apiAttr.url,entryType:ApiType.XHR,duration:getCurrentTime()-s.timestamp};if(isFunction(a))try{let e;s.success===ResourceStatus.Failed&&(e=new Error(ApiType.XHR+" error"));const t=await(a?.(this.apiCache,r,e,this));this.apiAttr=s={...s,...reviseApiAttr(t)}}catch(e){logger.warn("api-collector","evaluateApi failed for XHR",e)}t.sendAPI(s,n)}),this.addEventListener("error",()=>{this.apiAttr&&(this.apiAttr.success=ResourceStatus.Failed)}),this.addEventListener("abort",()=>{this.apiAttr&&(this.apiAttr.success=ResourceStatus.Failed,this.apiAttr.message="abort")})},!0)}hackFetch(){const e=this,t=this.originFetch=globalThis.fetch,s=this.ctx.getConfig().filters||{};globalThis.fetch=function(i,r={}){const a=i instanceof Request,n=a?i.url:isString(i)?i:i.toString();let c=r.method,o=r.headers;if(a&&(!c&&(c=i.method),!o&&(o=i.headers)),c=c?c.toUpperCase():"GET",o||(r.headers=o=new Headers),urlMatch(n,s.resource)||"HEAD"===c||"no-cors"===r?.mode||n.length>8182)return t.call(global,i,r);let p,u,h={...e.ctx.session.getBaseEvent(),url:n,type:ApiType.FETCH,method:c};async function d(){const{evaluateApi:t}=e.ctx.getConfig(),s={name:h.url,entryType:ApiType.FETCH,duration:getCurrentTime()-h.timestamp};if(isFunction(t))try{const e=await(t?.({url:i,...r},p,u));h={...h,...reviseApiAttr(e)}}catch(e){logger.warn("api-collector","evaluateApi failed for fetch",e)}e.sendAPI(h,s)}return e.injectTracing(h,e=>{Object.keys(e).forEach(t=>{o&&(o instanceof Headers?e[t]&&o.set(t,e[t]):o[t]=e[t])})}),t.call(globalThis,i,r).then(e=>{if(e||isFunction(e.clone)){p=e.clone();const{status:t,statusText:s}=p;h.status_code=t,h.message=s,d().then()}return e}).catch(e=>{throw u=e,h.message=String(e),h.success=ResourceStatus.Failed,d().then(),e})}}sendAPI(e,t){const{parseResourceName:s}=this.ctx.getConfig(),{url:i,status_code:r,view:a}=e,n=getResourceSuccessByCode(r),c={duration:t.duration},o={event_type:RumEventType.RESOURCE,url:i,name:getResourceName(i||"",s),success:n,type:t.initiatorType||t.entryType,...e,timing_data:JSON.stringify(t),...c,view:a};this.sendEvent?.(o)}destroy(){this.sendEvent=()=>{};const e=XMLHttpRequest.prototype;restoreFunction(e,"open"),restoreFunction(e,"setRequestHeader"),restoreFunction(e,"send"),this.originFetch&&(globalThis.fetch=this.originFetch)}}
@@ -1,79 +1 @@
1
- // import { ICollector, IContext, isArray, RumEvent } from '@arms/rum-core';
2
- // import { getPerformance } from "../../utils/platform";
3
- // import { getCurView } from "../../utils/view";
4
- //
5
- //
6
- //
7
- //
8
- // export default class PerfCollector implements ICollector {
9
- // name = 'perf-collector';
10
- // ctx: IContext;
11
- // sendEvent: (payload: RumEvent) => void;
12
- // perfObserver: any;
13
- //
14
- // setup(ctx: IContext, sendEvent: (payload: RumEvent) => void) {
15
- // const {collectors = {}} = ctx.getConfig();
16
- // if (collectors.perf === false) {
17
- // return;
18
- // }
19
- // this.ctx = ctx;
20
- // this.sendEvent = sendEvent;
21
- // const performance = getPerformance();
22
- // if (!performance || !performance.createObserver) return;
23
- // this.perfObserver = performance.createObserver(this.observerHandler)
24
- // this.perfObserver.observe({entryTypes: ['launch', 'navigation', 'render', 'paint', 'resource']});
25
- // }
26
- //
27
- // observerHandler = (ev) => {
28
- // const entries = isArray(ev) ? ev : ev.getEntries();
29
- // const perfData = {
30
- // view: getCurView(this.ctx)
31
- // };
32
- // if (Array.isArray(entries) && entries.length > 0) {
33
- // for (let i = 0; i < entries.length; i++) {
34
- // const item = entries[i];
35
- // if (typeof item.duration === 'number') {
36
- // switch (item.name) {
37
- // case 'appLaunch':
38
- // case 'app-click':
39
- // perfData['launch'] = item.duration;
40
- // break;
41
- // case 'firstPaint':
42
- // // case 'first-paint':
43
- // perfData['first-paint'] = item.duration;
44
- // break;
45
- // case 'firstRender':
46
- // perfData['first_render'] = item.duration;
47
- // break;
48
- // case 'firstContentfulPaint':
49
- // // case 'first-contentful-paint':
50
- // perfData['first_contentful_paint'] = item.duration;
51
- // break;
52
- // case 'largestContentfulPaint':
53
- // // case 'largest-contentful-paint':
54
- // perfData['largest_contentful_paint'] = item.duration;
55
- // break;
56
- // case 'resourceTiming':
57
- // case 'resource-timing':
58
- // perfData['timing_data'] = JSON.stringify(item);
59
- // break;
60
- // default:
61
- // break;
62
- // }
63
- // }
64
- // }
65
- // }
66
- //
67
- // if (!Object.keys(perfData).length) return;
68
- // // TODO 处理性能数据
69
- // // console.warn('perf-collector: ', perfData);
70
- // };
71
- //
72
- // destroy() {
73
- // if (this.perfObserver) {
74
- // this.perfObserver.disconnect();
75
- // }
76
- // }
77
- // }
78
- "use strict";
79
- //# sourceMappingURL=perf.js.map
1
+ "use strict";