@arms/rum-reactnative 0.0.2-bate.1 → 0.0.2-bate.3

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.
@@ -1,379 +0,0 @@
1
- import {
2
- find,
3
- generateGUID,
4
- generateSpanId,
5
- generateTraceId,
6
- ICollector,
7
- IContext,
8
- isString,
9
- interceptFunction,
10
- restoreFunction,
11
- isFunction,
12
- ITracingHeaders,
13
- makeTracingHeaders,
14
- matchList,
15
- parseTracingOptions,
16
- performDraw,
17
- RumEvent,
18
- RumEventType,
19
- RumResourceEvent,
20
- ResourceStatus,
21
- TraceOption,
22
- urlMatch,
23
- ITracingOption,
24
- PropagatorType,
25
- } from '@arms/rum-core';
26
- import { getCurrentTime } from '../../utils/base';
27
- import { IRNConfig } from '../../types/client';
28
- import {
29
- ApiType,
30
- IApiAttr,
31
- IApiCache,
32
- reviseApiAttr,
33
- getResourceName,
34
- getResourceSuccessByCode,
35
- } from '../../utils/api';
36
- import { RumSession } from '../../utils/session';
37
-
38
- declare global {
39
- interface XMLHttpRequest {
40
- apiAttr: IApiAttr;
41
- apiCache: IApiCache;
42
- }
43
- }
44
-
45
- export default class ApiCollector implements ICollector {
46
- name = 'api-collector';
47
-
48
- private ctx?: IContext;
49
- private sendEvent?: (payload: RumEvent) => void;
50
- private originFetch: unknown;
51
-
52
- setup(ctx: IContext, sendEvent: (payload: RumEvent) => void) {
53
- const collectors = ctx.getConfig().collectors || {};
54
- if (!collectors.api) {
55
- return;
56
- }
57
- this.sendEvent = sendEvent;
58
- this.ctx = ctx;
59
- // this.perfObserver();
60
- this.hackXhr();
61
- // this.hackFetch();
62
- }
63
-
64
- injectTracing(
65
- apiAttr: IApiAttr,
66
- inject: (tracingHeaders: ITracingHeaders) => void
67
- ) {
68
- const {
69
- tracing = false,
70
- pid,
71
- version = '1.0.0',
72
- } = (this.ctx as IContext).getConfig();
73
- const {
74
- enable,
75
- sample,
76
- propagatorTypes,
77
- allowedUrls,
78
- tracestate,
79
- baggage = false,
80
- } = parseTracingOptions(tracing as ITracingOption);
81
- if (!enable) return;
82
- // @ts-ignore
83
- const traceOption = find(allowedUrls, (traceOption: TraceOption) =>
84
- matchList([traceOption.match], apiAttr.url || '')
85
- ) as TraceOption;
86
- if (!traceOption) return;
87
- let pTypes = traceOption.propagatorTypes;
88
- if (pTypes.length === 0) {
89
- pTypes = propagatorTypes as PropagatorType[];
90
- }
91
- // skywalking
92
- let isSw8 = false;
93
- if (pTypes.indexOf('sw8') > -1) {
94
- pTypes = ['sw8'];
95
- isSw8 = true;
96
- }
97
- const session = (this.ctx as IContext).session;
98
- const traceId = isSw8 ? generateGUID() : generateTraceId();
99
- const spanId = isSw8 ? generateGUID() : generateSpanId();
100
- const sampled = performDraw(sample as number);
101
- const uid = session.getUserId();
102
- const sid = session.getSessionId();
103
- const traceContext = tracestate
104
- ? `rum=v2&browser&${pid}&${sid}&${uid}`
105
- : undefined;
106
- const baggageContext = baggage
107
- ? `rum=v2,appType=browser,pid=${pid},sid=${sid},uid=${uid}`
108
- : undefined;
109
-
110
- const tracingHeaders = makeTracingHeaders(
111
- traceId,
112
- spanId,
113
- sampled,
114
- pTypes,
115
- {
116
- tracestate: traceContext,
117
- baggage: baggageContext,
118
- appId: pid,
119
- appVersion: version,
120
- viewName: apiAttr.view?.name,
121
- // host: location.host
122
- }
123
- );
124
- if (sampled) {
125
- apiAttr.trace_id = traceId;
126
- apiAttr.trace_data = JSON.stringify({
127
- spanId,
128
- sample,
129
- sampled,
130
- headers: tracingHeaders,
131
- });
132
- }
133
- inject(tracingHeaders);
134
- }
135
-
136
- hackXhr() {
137
- const oldXHRProto = XMLHttpRequest.prototype;
138
- const self = this;
139
- interceptFunction(
140
- oldXHRProto,
141
- 'open',
142
- function (
143
- this: XMLHttpRequest,
144
- method: string,
145
- url: string | URL,
146
- async?: boolean,
147
- user?: string,
148
- password?: string
149
- ) {
150
- const filters = (self.ctx as IContext).getConfig().filters || {};
151
- const urlStr = String(url);
152
- const methodStr = method.toUpperCase();
153
- // chrome 对 URL 的最大长度为 8182 个字符
154
- if (
155
- urlMatch(urlStr, filters.resource) ||
156
- methodStr === 'HEAD' ||
157
- urlStr.length > 8182
158
- )
159
- return;
160
- this.apiCache = {
161
- type: ApiType.XHR,
162
- method,
163
- url,
164
- async,
165
- user,
166
- password,
167
- headers: {},
168
- };
169
- const baseRumEvent = (
170
- (self.ctx as IContext).session as RumSession
171
- ).getBaseEvent();
172
- this.apiAttr = {
173
- ...baseRumEvent,
174
- url: urlStr,
175
- method: methodStr,
176
- } as IApiAttr;
177
- },
178
- true
179
- );
180
- interceptFunction(
181
- oldXHRProto,
182
- 'setRequestHeader',
183
- function (this: XMLHttpRequest, header: string, value: string) {
184
- if (!this.apiCache) return;
185
- this.apiCache.headers = {
186
- ...this.apiCache.headers,
187
- [header]: value,
188
- };
189
- },
190
- true
191
- );
192
- interceptFunction(
193
- oldXHRProto,
194
- 'send',
195
- function (this: XMLHttpRequest, body: any) {
196
- if (!this.apiAttr || !this.apiCache) return;
197
- let apiAttr = this.apiAttr;
198
- this.apiCache.data = body;
199
- self.injectTracing(this.apiAttr, (tracingHeaders) => {
200
- Object.keys(tracingHeaders).forEach((key) => {
201
- tracingHeaders[key] && this.setRequestHeader(key, tracingHeaders[key])
202
- });
203
- });
204
- this.addEventListener('loadend', async () => {
205
- const { status, statusText, response } = this;
206
- const { evaluateApi } = (self.ctx as IContext).getConfig() as IRNConfig;
207
- if (!this.apiAttr) return;
208
- apiAttr.status_code = status;
209
- apiAttr.message = statusText;
210
- const entry = {
211
- name: this.apiAttr.url,
212
- entryType: ApiType.XHR,
213
- duration: getCurrentTime() - apiAttr.timestamp,
214
- };
215
- if (isFunction(evaluateApi)) {
216
- try {
217
- let error: Error | undefined;
218
- if (apiAttr.success === ResourceStatus.Failed) {
219
- error = new Error(ApiType.XHR + ' error');
220
- }
221
- const attrs = await evaluateApi?.(
222
- this.apiCache,
223
- response,
224
- error,
225
- this
226
- );
227
- this.apiAttr = apiAttr = {
228
- ...apiAttr,
229
- ...reviseApiAttr(attrs as IApiAttr),
230
- };
231
- } catch (e) {
232
- //
233
- }
234
- }
235
- self.sendAPI(apiAttr, entry);
236
- });
237
- this.addEventListener('error', () => {
238
- this.apiAttr && (this.apiAttr.success = ResourceStatus.Failed);
239
- });
240
- },
241
- true
242
- );
243
- }
244
-
245
- hackFetch() {
246
- const self = this;
247
- const originFetch = (this.originFetch = globalThis.fetch);
248
- const filters = (this.ctx as IContext).getConfig().filters || {};
249
- globalThis.fetch = function (
250
- input: Request | URL | string,
251
- init: RequestInit = {}
252
- ) {
253
- const isReq = input instanceof Request;
254
- const urlStr = isReq
255
- ? input.url
256
- : isString(input)
257
- ? input
258
- : input.toString();
259
- let method = init.method;
260
- let headers = init.headers;
261
- if (isReq) {
262
- !method && (method = input.method);
263
- !headers && (headers = input.headers);
264
- }
265
- method = method ? method.toUpperCase() : 'GET';
266
- if (!headers) {
267
- init.headers = headers = new Headers();
268
- }
269
- if (
270
- urlMatch(urlStr, filters.resource) ||
271
- method === 'HEAD' ||
272
- init?.mode === 'no-cors' ||
273
- urlStr.length > 8182
274
- ) {
275
- return originFetch.call(global, input, init);
276
- }
277
- const baseRumEvent = ((self.ctx as IContext).session as RumSession).getBaseEvent();
278
- let apiAttr = {
279
- ...baseRumEvent,
280
- url: urlStr,
281
- type: ApiType.FETCH,
282
- method,
283
- } as IApiAttr;
284
- let apiResp: Response;
285
- let apiError: Error;
286
-
287
- self.injectTracing(apiAttr, (tracingHeaders) => {
288
- Object.keys(tracingHeaders).forEach((key) => {
289
- if(!headers) return;
290
- if (headers instanceof Headers) {
291
- tracingHeaders[key] && headers.set(key, tracingHeaders[key]);
292
- } else {
293
- // @ts-ignore
294
- headers[key] = tracingHeaders[key];
295
- }
296
- });
297
- });
298
-
299
- async function fetchFinally() {
300
- const { evaluateApi } = (self.ctx as IContext).getConfig() as IRNConfig;
301
- const entry = {
302
- name: apiAttr.url,
303
- entryType: ApiType.FETCH,
304
- duration: getCurrentTime() - apiAttr.timestamp,
305
- };
306
- if (isFunction(evaluateApi)) {
307
- try {
308
- const attrs = await evaluateApi?.(
309
- { url: input, ...init },
310
- apiResp,
311
- apiError
312
- );
313
- apiAttr = { ...apiAttr, ...reviseApiAttr(attrs as IApiAttr) };
314
- } catch (e) {
315
- //
316
- }
317
- }
318
- self.sendAPI(apiAttr, entry);
319
- }
320
-
321
- return originFetch
322
- .call(globalThis, input, init)
323
- .then((response: Response) => {
324
- // @ts-ignore
325
- if (response || isFunction(response.clone)) {
326
- apiResp = response.clone();
327
- const { status, statusText } = apiResp;
328
- apiAttr.status_code = status;
329
- apiAttr.message = statusText;
330
- fetchFinally().then();
331
- }
332
- return response;
333
- })
334
- .catch((error: Error) => {
335
- apiError = error;
336
- apiAttr.message = String(error);
337
- apiAttr.success = ResourceStatus.Failed;
338
- fetchFinally().then();
339
- throw error;
340
- });
341
- };
342
- }
343
-
344
- sendAPI(apiAttr: IApiAttr, entry: any) {
345
- const { parseResourceName } = (this.ctx as IContext).getConfig() as IRNConfig;
346
- const { url, status_code, view } = apiAttr;
347
- const success = getResourceSuccessByCode(status_code as string);
348
- const measures = {
349
- duration: entry.duration,
350
- };
351
- const event: RumResourceEvent = {
352
- event_type: RumEventType.RESOURCE,
353
- url,
354
- name: getResourceName(url || '', parseResourceName),
355
- success,
356
- type: entry.initiatorType || entry.entryType,
357
- ...apiAttr,
358
- timing_data: JSON.stringify(entry),
359
- // Measure
360
- ...measures,
361
- view,
362
- };
363
-
364
- this.sendEvent?.(event);
365
- }
366
-
367
- destroy() {
368
- this.sendEvent = () => {};
369
- // 恢复XHR
370
- const oldXHRProto = XMLHttpRequest.prototype;
371
- restoreFunction(oldXHRProto, 'open');
372
- restoreFunction(oldXHRProto, 'setRequestHeader');
373
- restoreFunction(oldXHRProto, 'send');
374
- // 恢复 fetch
375
- if (this.originFetch) {
376
- globalThis.fetch = this.originFetch as any;
377
- }
378
- }
379
- }
@@ -1,77 +0,0 @@
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
- // }
@@ -1,73 +0,0 @@
1
- import { ICollector, IContext, isFunction, IViewData, RumEvent, RumEventType, RumViewEvent } from '@arms/rum-core';
2
- import { IRNConfig, IPage } from "../../types/client";
3
- import { addNavigateListener, removeNavigateListener } from "../../utils/view";
4
- import { Route } from "../../types";
5
-
6
- export default class PvCollector implements ICollector {
7
- name = 'pv-collector';
8
- ctx?: IContext;
9
- sendEvent?: (payload: RumEvent) => void;
10
-
11
- private prevPage?: IPage;
12
-
13
- private pageMap: { [key: string]: IPage } = {};
14
-
15
- setup(ctx: IContext, sendEvent: (payload: RumEvent) => void) {
16
- this.ctx = ctx;
17
- this.sendEvent = sendEvent;
18
- addNavigateListener(this.onChange)
19
- }
20
-
21
- onChange = (route: Route<any>) => {
22
- // {"name":"index","path":"/","key":"index-SB2hXmOGNdpiLskG5sXwj"}
23
- // {"name":"__root","key":"__root-jPbNqG7-HaM-OUx_FcCUw"}
24
- const {key, name, path, params} = route;
25
- const page: IPage = {name, path, id: key, params};
26
- if (name === "__root" || !('path' in page)) return;
27
- this.sendPv(page);
28
- }
29
-
30
- sendPv = (page: IPage) => {
31
- const prevPage = this.prevPage;
32
- const ctx = this.ctx as IContext;
33
- if (page.id === prevPage?.id) {
34
- // 路由没有发生变化,不发送
35
- return;
36
- }
37
- page.name = this.getViewName(page.path as string, page);
38
-
39
- if (prevPage && prevPage.id in this.pageMap) {
40
- delete this.pageMap[prevPage.id];
41
- ctx.removeView(prevPage.id)
42
- }
43
- this.prevPage = page;
44
-
45
- ctx.addView(page as IViewData);
46
-
47
- // 路由回退情况下已经上报
48
- if(page.id in this.pageMap) {
49
- return;
50
- }
51
-
52
- this.pageMap[page.id] = page;
53
- this.sendEvent?.({
54
- event_type: RumEventType.VIEW,
55
- type: 'pv',
56
- loading_type: 'initial_load',
57
- ...page,
58
- } as RumViewEvent);
59
- }
60
-
61
- private getViewName(url: string, page: IPage): string | undefined {
62
- let { parseViewName } = this.ctx?.getConfig() as IRNConfig;
63
- let name = page.name;
64
- if (parseViewName && isFunction(parseViewName)) {
65
- name = parseViewName(url, page) || name;
66
- }
67
- return name
68
- }
69
-
70
- destroy() {
71
- removeNavigateListener(this.onChange);
72
- }
73
- }
package/src/index.ts DELETED
@@ -1,4 +0,0 @@
1
- import armsRum, { ArmsRum } from './shell';
2
-
3
- export { ArmsRum };
4
- export default armsRum;
package/src/plugin.ts DELETED
@@ -1,41 +0,0 @@
1
- import { NativeModules, NativeEventEmitter } from 'react-native';
2
- import { isString } from "@arms/rum-core";
3
- import { IRNConfig } from "./types/client";
4
- import { VERSION } from "./utils/platform";
5
- import armsRum from './shell';
6
-
7
- const RumSdkModule = NativeModules.RumSdkModule;
8
-
9
- function initRum(config: IRNConfig) {
10
- armsRum.init({
11
- ...config,
12
- beforeReport(bundle): any {
13
- RumSdkModule.sendEvent(JSON.stringify(bundle));
14
- return null
15
- }
16
- })
17
- }
18
-
19
- if (RumSdkModule) {
20
- const eventEmitter = new NativeEventEmitter(RumSdkModule);
21
- eventEmitter.addListener('rumNativeEvent', (config: IRNConfig) => {
22
- if (isString(config)) {
23
- try {
24
- config = JSON.parse(config);
25
- } catch (e) {
26
- console.warn('getConfig error', e);
27
- return
28
- }
29
- }
30
- if (!config.pid) {
31
- config.pid = `react-native@${VERSION}`;
32
- }
33
- if (!config.endpoint) {
34
- config.endpoint = `http://127.0.0.1/?version=${VERSION}`;
35
- }
36
- initRum(config);
37
- });
38
- RumSdkModule?.getConfig?.();
39
- }
40
-
41
- export default armsRum
@@ -1,22 +0,0 @@
1
- import { IContext, IProcessor, IViewData, RumEvent } from '@arms/rum-core';
2
- import { getCurrentTime } from '../utils/base';
3
- import { getCurView } from '../utils/view';
4
-
5
- export default class DefaultProcessor implements IProcessor {
6
- name = 'default-processor';
7
-
8
- process(ctx: IContext) {
9
- const event = ctx.getRumEvent();
10
- const view = getCurView(ctx) as IViewData;
11
- const session_id = ctx.session.getSessionId();
12
- const event_id = ctx.session.getEventId();
13
- const rumEvent: RumEvent = {
14
- timestamp: getCurrentTime(),
15
- session_id,
16
- event_id,
17
- view,
18
- ...event,
19
- };
20
- return rumEvent;
21
- }
22
- }
@@ -1,23 +0,0 @@
1
- import { debounce, IContext, IProcessor, ONE_SECOND } from '@arms/rum-core';
2
-
3
- export default class SessionProcessor implements IProcessor {
4
- name = 'session-processor';
5
-
6
- ctx?: IContext;
7
-
8
- setup(ctx: IContext) {
9
- this.ctx = ctx;
10
- this.update = debounce(this.update, ONE_SECOND);
11
- // sdk.onTouchStart(this.update)
12
- }
13
-
14
- update = (e?: Event) => {
15
- if (e && !e.isTrusted) return;
16
- this.ctx?.session.updateSession();
17
- }
18
-
19
- process(ctx: IContext) {
20
- this.update();
21
- return ctx.getRumEvent();
22
- }
23
- }
@@ -1,67 +0,0 @@
1
- import {
2
- IContext,
3
- IReporter,
4
- Reporter,
5
- RumEventBundle,
6
- } from '@arms/rum-core';
7
- import { fixAttrs } from '../utils/platform';
8
- // @ts-ignore
9
- import { AppState, NativeEventSubscription } from 'react-native';
10
-
11
- /**
12
- * rn-app reporter
13
- */
14
- class RNReporter extends Reporter implements IReporter {
15
- name = 'rn-reporter';
16
-
17
- private appStateListener?: NativeEventSubscription;
18
-
19
- constructor() {
20
- super();
21
- // 离开之前 flushEventQueue
22
- try {
23
- this.appStateListener = AppState.addEventListener('change', this.onAppStateChanged);
24
- } catch (error) {
25
- //
26
- console.info(error)
27
- }
28
- }
29
-
30
- init(ctx: IContext) {
31
- this.ctx = ctx;
32
- }
33
-
34
- onAppStateChanged(nextAppState: string) {
35
- if (nextAppState === 'background') {
36
- // 执行后台操作:暂停动画、保存数据、停止网络请求等
37
- this.flushEventQueue();
38
- }
39
- // else if (nextAppState === 'active') {
40
- // console.warn('RUM:应用回到前台');
41
- // }
42
- }
43
-
44
- request(ctx: IContext, bundle: RumEventBundle) {
45
- const batchedEvents = fixAttrs(bundle);
46
- const config = ctx.getConfig();
47
- fetch(config.endpoint, {
48
- method: 'POST',
49
- headers: {
50
- 'Content-Type': 'text/plain',
51
- },
52
- body: JSON.stringify(batchedEvents)
53
- }).then((_res) => {
54
- //
55
- }).catch(this.catchHandle)
56
- }
57
-
58
- catchHandle = (err: any) => {
59
- console.warn('[rum] sendRequest fail', err);
60
- }
61
-
62
- destroy() {
63
- this.appStateListener?.remove();
64
- }
65
- }
66
-
67
- export default RNReporter;