polyv-rum-sdk 0.1.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.
- package/.github/workflows/publish.yml +34 -0
- package/README.md +168 -0
- package/dist/index.d.mts +300 -0
- package/dist/index.d.ts +300 -0
- package/dist/index.js +1657 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1599 -0
- package/dist/index.mjs.map +1 -0
- package/jest.config.cjs +8 -0
- package/package.json +33 -0
- package/src/__tests__/SLSWebTrackingAdapter.test.ts +43 -0
- package/src/__tests__/config.test.ts +76 -0
- package/src/core/MitoSLSAdapter.ts +468 -0
- package/src/core/config.ts +338 -0
- package/src/core/env.ts +17 -0
- package/src/index.ts +26 -0
- package/src/transport/SLSWebTrackingAdapter.ts +602 -0
- package/src/types/external.d.ts +8 -0
- package/src/vue2/RUMManager.vue2.ts +455 -0
- package/src/vue2/plugin.vue2.ts +56 -0
- package/src/vue3/RUMManager.vue3.ts +56 -0
- package/src/vue3/plugin.vue3.ts +40 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { init } from '@mitojs/browser';
|
|
2
|
+
import {
|
|
3
|
+
mitoConfig,
|
|
4
|
+
slsConfig,
|
|
5
|
+
transformDataForSLS,
|
|
6
|
+
shouldReport,
|
|
7
|
+
type MitoConfig,
|
|
8
|
+
type TransformContext
|
|
9
|
+
} from './config';
|
|
10
|
+
import {
|
|
11
|
+
SLSWebTrackingAdapter,
|
|
12
|
+
type SLSAdapterConfig
|
|
13
|
+
} from '../transport/SLSWebTrackingAdapter';
|
|
14
|
+
|
|
15
|
+
export interface MitoSLSAdapterOptions extends MitoConfig {
|
|
16
|
+
slsConfigOverride?: Partial<SLSAdapterConfig>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class MitoSLSAdapter {
|
|
20
|
+
private options: MitoSLSAdapterOptions;
|
|
21
|
+
private mitoInstance: any = null;
|
|
22
|
+
private slsAdapter: SLSWebTrackingAdapter | null = null;
|
|
23
|
+
private store: any = null;
|
|
24
|
+
private router: any = null;
|
|
25
|
+
private isInitialized = false;
|
|
26
|
+
|
|
27
|
+
constructor(options: Partial<MitoSLSAdapterOptions> = {}) {
|
|
28
|
+
this.options = {
|
|
29
|
+
...mitoConfig,
|
|
30
|
+
...options
|
|
31
|
+
} as MitoSLSAdapterOptions;
|
|
32
|
+
|
|
33
|
+
this.handleDataReport = this.handleDataReport.bind(this);
|
|
34
|
+
this.handleRouteChange = this.handleRouteChange.bind(this);
|
|
35
|
+
this.handleError = this.handleError.bind(this);
|
|
36
|
+
this.addBreadcrumb = this.addBreadcrumb.bind(this);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
addBreadcrumb(payload: any): void {
|
|
40
|
+
if (!this.mitoInstance || !this.mitoInstance.breadcrumb) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const breadcrumb = this.mitoInstance.breadcrumb;
|
|
45
|
+
|
|
46
|
+
if (typeof breadcrumb.add === 'function') {
|
|
47
|
+
breadcrumb.add(payload);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof breadcrumb.push === 'function') {
|
|
52
|
+
breadcrumb.push(payload);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async init(context: TransformContext & { Vue?: any } = {}): Promise<void> {
|
|
57
|
+
if (this.isInitialized) {
|
|
58
|
+
console.warn('MitoSLSAdapter already initialized');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.store = context.store;
|
|
63
|
+
this.router = context.router;
|
|
64
|
+
|
|
65
|
+
if (!this.options.dsn) {
|
|
66
|
+
console.warn(
|
|
67
|
+
'MitoSLSAdapter: DSN not configured, RUM system will run in simulation mode'
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const slsConfigOverride = this.options.slsConfigOverride || {};
|
|
73
|
+
this.slsAdapter = new SLSWebTrackingAdapter({
|
|
74
|
+
...slsConfig,
|
|
75
|
+
...slsConfigOverride
|
|
76
|
+
});
|
|
77
|
+
await this.slsAdapter.init();
|
|
78
|
+
|
|
79
|
+
this.mitoInstance = init({
|
|
80
|
+
dsn: this.options.dsn || undefined,
|
|
81
|
+
debug: this.options.debug,
|
|
82
|
+
maxBreadcrumbs: this.options.maxBreadcrumbs,
|
|
83
|
+
Vue: context.Vue || this.options.vue?.Vue,
|
|
84
|
+
framework: {
|
|
85
|
+
vue: true
|
|
86
|
+
},
|
|
87
|
+
beforeDataReport: this.handleDataReport,
|
|
88
|
+
silent: !this.options.debug,
|
|
89
|
+
enableUrlHash: true,
|
|
90
|
+
enableUserAgent: true,
|
|
91
|
+
enableHistory: true,
|
|
92
|
+
performance: {
|
|
93
|
+
enable: this.options.user.performance,
|
|
94
|
+
sampleRate: (this.options.sampleRate as any).performance || 1.0
|
|
95
|
+
},
|
|
96
|
+
user: {
|
|
97
|
+
enableClickTrack: this.options.user.click,
|
|
98
|
+
clickTrackSampleRate: (this.options.sampleRate as any).click,
|
|
99
|
+
ignoreSelector: this.options.user.sensitiveSelectors.join(', ')
|
|
100
|
+
},
|
|
101
|
+
api: {
|
|
102
|
+
enableApiMonitor: this.options.network.xhr,
|
|
103
|
+
apiMonitorSampleRate: (this.options.sampleRate as any).xhr,
|
|
104
|
+
ignoreUrls: this.options.network.ignoreUrls,
|
|
105
|
+
requestSizeLimit: this.options.network.responseSizeLimit * 1024
|
|
106
|
+
}
|
|
107
|
+
} as any);
|
|
108
|
+
|
|
109
|
+
this.setupVueIntegration();
|
|
110
|
+
this.setupRouterIntegration();
|
|
111
|
+
this.setupErrorHandling();
|
|
112
|
+
|
|
113
|
+
this.isInitialized = true;
|
|
114
|
+
if (this.options.debug) {
|
|
115
|
+
console.log('MitoSLSAdapter initialized successfully');
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error('Failed to initialize MitoSLSAdapter:', error);
|
|
119
|
+
this.handleError(error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private setupVueIntegration(): void {
|
|
124
|
+
if (!this.store) {
|
|
125
|
+
console.warn('Vuex store not provided, some features may be limited');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.store.subscribe((mutation: any) => {
|
|
130
|
+
try {
|
|
131
|
+
if (this.shouldTrackMutation(mutation)) {
|
|
132
|
+
this.addBreadcrumb({
|
|
133
|
+
type: 'vuex',
|
|
134
|
+
message: `Vuex: ${mutation.type}`,
|
|
135
|
+
category: 'vuex',
|
|
136
|
+
data: {
|
|
137
|
+
mutation: mutation.type,
|
|
138
|
+
payload: mutation.payload
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.warn('Failed to track Vuex mutation:', error);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (this.options.debug) {
|
|
148
|
+
console.log('Vue integration configured');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private setupRouterIntegration(): void {
|
|
153
|
+
if (!this.router) {
|
|
154
|
+
console.warn('Vue router not provided, route tracking disabled');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.router.afterEach((to: any, from: any) => {
|
|
159
|
+
try {
|
|
160
|
+
this.handleRouteChange(to, from);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.warn('Failed to handle route change:', error);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (this.options.debug) {
|
|
167
|
+
console.log('Router integration configured');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private setupErrorHandling(): void {
|
|
172
|
+
if (this.options.debug) {
|
|
173
|
+
console.log('Error handling configured');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
handleDataReport(data: any): boolean | void {
|
|
178
|
+
try {
|
|
179
|
+
if (this.options.debug) {
|
|
180
|
+
console.log('🔍 MitoJS Data Report:', {
|
|
181
|
+
type: data.type,
|
|
182
|
+
timestamp: data.t || Date.now(),
|
|
183
|
+
data
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!shouldReport(data)) {
|
|
188
|
+
if (this.options.debug) {
|
|
189
|
+
console.log('⏭️ Data filtered by sampling rate:', data.type);
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const slsData = transformDataForSLS(data, {
|
|
195
|
+
store: this.store,
|
|
196
|
+
router: this.router
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
this.sendToSLS(slsData);
|
|
200
|
+
|
|
201
|
+
return false;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error('Failed to handle data report:', error);
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
handleRouteChange(to: any, from: any): void {
|
|
209
|
+
try {
|
|
210
|
+
const routeData = {
|
|
211
|
+
type: 'route',
|
|
212
|
+
from: {
|
|
213
|
+
path: from.fullPath,
|
|
214
|
+
name: from.name,
|
|
215
|
+
params: from.params,
|
|
216
|
+
query: from.query
|
|
217
|
+
},
|
|
218
|
+
to: {
|
|
219
|
+
path: to.fullPath,
|
|
220
|
+
name: to.name,
|
|
221
|
+
params: to.params,
|
|
222
|
+
query: to.query
|
|
223
|
+
},
|
|
224
|
+
timestamp: Date.now()
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
this.addBreadcrumb({
|
|
228
|
+
type: 'route',
|
|
229
|
+
message: `Route: ${from.fullPath} -> ${to.fullPath}`,
|
|
230
|
+
category: 'navigation',
|
|
231
|
+
data: routeData
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (shouldReport(routeData)) {
|
|
235
|
+
const slsData = transformDataForSLS(routeData, {
|
|
236
|
+
store: this.store,
|
|
237
|
+
router: this.router
|
|
238
|
+
});
|
|
239
|
+
this.sendToSLS(slsData);
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error('Failed to handle route change:', error);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
handleError(error: any): void {
|
|
247
|
+
try {
|
|
248
|
+
const errorData = {
|
|
249
|
+
type: 'error',
|
|
250
|
+
message: error?.message || 'Unknown error',
|
|
251
|
+
stack: error?.stack,
|
|
252
|
+
name: error?.name,
|
|
253
|
+
timestamp: Date.now()
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
this.addBreadcrumb({
|
|
257
|
+
type: 'error',
|
|
258
|
+
message: errorData.message,
|
|
259
|
+
category: 'error',
|
|
260
|
+
data: errorData
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
if (shouldReport(errorData)) {
|
|
264
|
+
const slsData = transformDataForSLS(errorData, {
|
|
265
|
+
store: this.store,
|
|
266
|
+
router: this.router
|
|
267
|
+
});
|
|
268
|
+
this.sendToSLS(slsData);
|
|
269
|
+
}
|
|
270
|
+
} catch (e) {
|
|
271
|
+
console.error('Failed to handle error:', e);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async sendToSLS(data: any): Promise<void> {
|
|
276
|
+
try {
|
|
277
|
+
if (!this.slsAdapter) {
|
|
278
|
+
if (this.options.debug) {
|
|
279
|
+
console.log('📝 RUM Data [SIMULATION MODE]:', data);
|
|
280
|
+
}
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const slsLogData = this.transformDataForSLSLog(data);
|
|
285
|
+
if (this.options.debug) {
|
|
286
|
+
console.log('🔧 Transformed SLS Log Data:', slsLogData);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
await this.slsAdapter.sendLog(slsLogData);
|
|
290
|
+
|
|
291
|
+
if (this.options.debug) {
|
|
292
|
+
console.log('✅ RUM Data sent to SLS successfully');
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error('❌ Failed to send data to SLS:', error);
|
|
296
|
+
|
|
297
|
+
if (this.options.debug) {
|
|
298
|
+
console.log('🔄 RUM Data [FALLBACK MODE]:', data);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
transformDataForSLSLog(data: any): any {
|
|
304
|
+
return {
|
|
305
|
+
eventType: data.dataType || data.event?.type || 'unknown',
|
|
306
|
+
category: this.getEventCategory(data.dataType),
|
|
307
|
+
level: this.getEventLevel(data.dataType),
|
|
308
|
+
...data.event,
|
|
309
|
+
...data.dimensions,
|
|
310
|
+
...data.rawData
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
getEventCategory(dataType: string): string {
|
|
315
|
+
switch (dataType) {
|
|
316
|
+
case 'error':
|
|
317
|
+
return 'error';
|
|
318
|
+
case 'performance':
|
|
319
|
+
return 'performance';
|
|
320
|
+
case 'click':
|
|
321
|
+
case 'route':
|
|
322
|
+
return 'user';
|
|
323
|
+
case 'xhr':
|
|
324
|
+
return 'general';
|
|
325
|
+
default:
|
|
326
|
+
return 'general';
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
getEventLevel(dataType: string): string {
|
|
331
|
+
switch (dataType) {
|
|
332
|
+
case 'error':
|
|
333
|
+
return 'error';
|
|
334
|
+
case 'xhr':
|
|
335
|
+
return 'warn';
|
|
336
|
+
default:
|
|
337
|
+
return 'info';
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
getUserInfoFromStore(): any {
|
|
342
|
+
try {
|
|
343
|
+
const user = this.store?.state?.user || {};
|
|
344
|
+
return {
|
|
345
|
+
userId: user.userId || user.id,
|
|
346
|
+
userName: user.userName || user.name,
|
|
347
|
+
accountId: user.accountId
|
|
348
|
+
};
|
|
349
|
+
} catch (error) {
|
|
350
|
+
console.warn('Failed to get user info from store:', error);
|
|
351
|
+
return {};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
shouldTrackMutation(mutation: any): boolean {
|
|
356
|
+
const ignoredMutations = ['SET_LOADING', 'SET_CURRENT_PAGE', 'UPDATE_MOUSE_POSITION'];
|
|
357
|
+
return !ignoredMutations.includes(mutation.type);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
trackEvent(eventData: Record<string, any>): void {
|
|
361
|
+
try {
|
|
362
|
+
const customData = {
|
|
363
|
+
type: 'custom',
|
|
364
|
+
timestamp: Date.now(),
|
|
365
|
+
...eventData
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
this.addBreadcrumb({
|
|
369
|
+
type: 'custom',
|
|
370
|
+
message: `Custom Event: ${eventData.name || 'unknown'}`,
|
|
371
|
+
category: 'custom',
|
|
372
|
+
data: customData
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
if (shouldReport(customData)) {
|
|
376
|
+
const slsData = transformDataForSLS(customData, {
|
|
377
|
+
store: this.store,
|
|
378
|
+
router: this.router
|
|
379
|
+
});
|
|
380
|
+
this.sendToSLS(slsData);
|
|
381
|
+
}
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error('Failed to track custom event:', error);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
setUser(userInfo: {
|
|
388
|
+
userId?: string;
|
|
389
|
+
userName?: string;
|
|
390
|
+
email?: string;
|
|
391
|
+
accountId?: string;
|
|
392
|
+
roles?: string[];
|
|
393
|
+
}): void {
|
|
394
|
+
try {
|
|
395
|
+
if (this.mitoInstance && this.mitoInstance.setUser) {
|
|
396
|
+
this.mitoInstance.setUser({
|
|
397
|
+
id: userInfo.userId,
|
|
398
|
+
username: userInfo.userName,
|
|
399
|
+
email: userInfo.email
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (this.slsAdapter && userInfo.userId) {
|
|
404
|
+
this.slsAdapter.setUserId(userInfo.userId);
|
|
405
|
+
}
|
|
406
|
+
} catch (error) {
|
|
407
|
+
console.error('Failed to set user info:', error);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
getBreadcrumbs(): any[] {
|
|
412
|
+
try {
|
|
413
|
+
return this.mitoInstance?.getBreadcrumbs?.() || [];
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.error('Failed to get breadcrumbs:', error);
|
|
416
|
+
return [];
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
destroy(): void {
|
|
421
|
+
try {
|
|
422
|
+
if (typeof window !== 'undefined') {
|
|
423
|
+
window.removeEventListener('error', this.handleError as any);
|
|
424
|
+
window.removeEventListener(
|
|
425
|
+
'unhandledrejection',
|
|
426
|
+
this.handleError as any
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (this.mitoInstance && this.mitoInstance.destroy) {
|
|
431
|
+
this.mitoInstance.destroy();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (this.slsAdapter) {
|
|
435
|
+
this.slsAdapter.destroy();
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
this.isInitialized = false;
|
|
439
|
+
if (this.options.debug) {
|
|
440
|
+
console.log('MitoSLSAdapter destroyed');
|
|
441
|
+
}
|
|
442
|
+
} catch (error) {
|
|
443
|
+
console.error('Failed to destroy MitoSLSAdapter:', error);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
let instance: MitoSLSAdapter | null = null;
|
|
449
|
+
|
|
450
|
+
export const getMitoAdapter = (
|
|
451
|
+
options: Partial<MitoSLSAdapterOptions> = {}
|
|
452
|
+
): MitoSLSAdapter => {
|
|
453
|
+
if (!instance) {
|
|
454
|
+
instance = new MitoSLSAdapter(options);
|
|
455
|
+
}
|
|
456
|
+
return instance;
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
export const initRUMCore = async (
|
|
460
|
+
context: TransformContext & { Vue?: any },
|
|
461
|
+
options: Partial<MitoSLSAdapterOptions> = {}
|
|
462
|
+
): Promise<MitoSLSAdapter> => {
|
|
463
|
+
const adapter = getMitoAdapter(options);
|
|
464
|
+
await adapter.init(context);
|
|
465
|
+
return adapter;
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
export const getRUMCoreInstance = (): MitoSLSAdapter | null => instance;
|