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.
@@ -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;