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,455 @@
1
+ import { mitoConfig, slsConfig, type TransformContext } from '../core/config';
2
+ import { getRUMEnv } from '../core/env';
3
+ import {
4
+ initRUMCore,
5
+ type MitoSLSAdapterOptions
6
+ } from '../core/MitoSLSAdapter';
7
+
8
+ export interface RUMManagerContext extends TransformContext {
9
+ Vue?: any;
10
+ }
11
+
12
+ export class RUMManagerVue2 {
13
+ private adapter: any = null;
14
+ private isInitialized = false;
15
+ private isEnabled = true;
16
+ private context: RUMManagerContext = {};
17
+
18
+ async init(
19
+ context: RUMManagerContext,
20
+ options: Partial<MitoSLSAdapterOptions> = {}
21
+ ): Promise<void> {
22
+ try {
23
+ if (!this.shouldEnableRUM()) {
24
+ if (mitoConfig.debug) {
25
+ console.log('RUM system is disabled');
26
+ }
27
+ return;
28
+ }
29
+
30
+ if (this.isInitialized) {
31
+ if (mitoConfig.debug) {
32
+ console.log('RUM system already initialized');
33
+ }
34
+ return;
35
+ }
36
+
37
+ this.context = {
38
+ store: context.store,
39
+ router: context.router,
40
+ Vue: context.Vue,
41
+ ...context
42
+ };
43
+
44
+ const mergedOptions: Partial<MitoSLSAdapterOptions> = {
45
+ ...mitoConfig,
46
+ ...options,
47
+ vue: {
48
+ ...(mitoConfig as any).vue,
49
+ ...(options as any).vue
50
+ }
51
+ };
52
+
53
+ this.adapter = await initRUMCore(
54
+ {
55
+ store: this.context.store,
56
+ router: this.context.router,
57
+ Vue: this.context.Vue
58
+ },
59
+ mergedOptions
60
+ );
61
+
62
+ this.setUserInfo();
63
+ this.setupUserStateListener();
64
+ this.setupUserInteractionTracking();
65
+
66
+ this.isInitialized = true;
67
+ if (mitoConfig.debug) {
68
+ console.log('RUM system initialized successfully');
69
+ }
70
+ } catch (error) {
71
+ console.error('Failed to initialize RUM system:', error);
72
+ }
73
+ }
74
+
75
+ private shouldEnableRUM(): boolean {
76
+ if (!this.isEnabled) {
77
+ return false;
78
+ }
79
+
80
+ const env = getRUMEnv();
81
+
82
+ const rumEnabled = (env as any).VUE_APP_RUM_ENABLED;
83
+ if (rumEnabled !== undefined) {
84
+ return rumEnabled === 'true';
85
+ }
86
+
87
+ const mode = (env as any).MODE;
88
+ const isProdEnv = mode === 'prod' || mode === 'production';
89
+
90
+ return !isProdEnv;
91
+ }
92
+
93
+ private setUserInfo(): void {
94
+ if (!this.context.store) {
95
+ return;
96
+ }
97
+
98
+ try {
99
+ const userModule = this.context.store.state.user || {};
100
+ const user = userModule.userInfo || userModule || {};
101
+
102
+ if (this.adapter && user.userId) {
103
+ this.adapter.setUser({
104
+ userId: user.userId,
105
+ userName: user.userName,
106
+ accountId: user.accountId,
107
+ roles: user.roles,
108
+ email: user.email
109
+ });
110
+ }
111
+ } catch (error) {
112
+ console.warn('Failed to set user info:', error);
113
+ }
114
+ }
115
+
116
+ private setupUserStateListener(): void {
117
+ if (!this.context.store) {
118
+ return;
119
+ }
120
+
121
+ this.context.store.subscribe((mutation: any) => {
122
+ if (mutation.type.includes('user')) {
123
+ this.setUserInfo();
124
+ }
125
+ });
126
+ }
127
+
128
+ private setupUserInteractionTracking(): void {
129
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
130
+ return;
131
+ }
132
+
133
+ document.addEventListener(
134
+ 'click',
135
+ (event: MouseEvent) => {
136
+ try {
137
+ const originalTarget = event.target as HTMLElement | null;
138
+
139
+ const findClosest = (el: any, selector: string): HTMLElement | null =>
140
+ el && typeof el.closest === 'function' ? el.closest(selector) : null;
141
+
142
+ let target = findClosest(originalTarget, '[rum-id],[rum-name]');
143
+
144
+ if (!target) {
145
+ target = findClosest(
146
+ originalTarget,
147
+ 'button, a, [role="button"], [role="link"]'
148
+ );
149
+ }
150
+
151
+ if (!target && originalTarget && window.getComputedStyle) {
152
+ const style = window.getComputedStyle(originalTarget);
153
+ if (style.cursor === 'pointer') {
154
+ target = originalTarget;
155
+ }
156
+ }
157
+
158
+ if (!target) {
159
+ return;
160
+ }
161
+
162
+ const getAttr = (el: any, name: string): string | null =>
163
+ el && typeof el.getAttribute === 'function'
164
+ ? el.getAttribute(name)
165
+ : null;
166
+
167
+ let bizId =
168
+ getAttr(target, 'rum-id') || getAttr(target, 'rum-name') || '';
169
+
170
+ if (!bizId) {
171
+ const tagName = target.tagName
172
+ ? target.tagName.toLowerCase()
173
+ : 'element';
174
+
175
+ const classList = (target.className || '')
176
+ .toString()
177
+ .split(/\s+/)
178
+ .filter((c: string) => c.trim());
179
+ const primaryClass = classList[0] || 'no-class';
180
+
181
+ const getLabel = (el: any): string => {
182
+ if (!el) return '';
183
+ const text = (el.textContent || '').trim();
184
+ if (text) return text;
185
+ const ariaLabel = getAttr(el, 'aria-label');
186
+ if (ariaLabel) return ariaLabel;
187
+ const title = getAttr(el, 'title');
188
+ if (title) return title;
189
+ const alt = getAttr(el, 'alt');
190
+ if (alt) return alt;
191
+ const placeholder = getAttr(el, 'placeholder');
192
+ if (placeholder) return placeholder;
193
+ const value = typeof el.value === 'string' ? el.value.trim() : '';
194
+ if (value) return value;
195
+ return '';
196
+ };
197
+
198
+ const label = getLabel(target);
199
+ const safeLabel =
200
+ (label || primaryClass).replace(/\s+/g, '_').slice(0, 32) ||
201
+ 'no_label';
202
+
203
+ const route = (this.context?.router as any)?.currentRoute || {};
204
+
205
+ const buildRouteKey = (r: any): string => {
206
+ try {
207
+ if (!r) return 'unknown_route';
208
+
209
+ if (r.name) {
210
+ return String(r.name);
211
+ }
212
+
213
+ if (Array.isArray(r.matched) && r.matched.length > 0) {
214
+ const record = r.matched[r.matched.length - 1];
215
+ if (record && record.path) {
216
+ return record.path;
217
+ }
218
+ }
219
+
220
+ if (r.path) {
221
+ return r.path;
222
+ }
223
+ } catch {
224
+ // ignore
225
+ }
226
+
227
+ return window.location.pathname || 'unknown_route';
228
+ };
229
+
230
+ const routeKey = buildRouteKey(route);
231
+
232
+ let indexSuffix = '';
233
+ try {
234
+ const parent = target.parentNode as HTMLElement | null;
235
+ if (parent && parent.children && parent.children.length) {
236
+ const siblings = Array.from(parent.children).filter(
237
+ (el) =>
238
+ el.tagName === target.tagName &&
239
+ (el as HTMLElement).className === target.className
240
+ );
241
+ const index = siblings.indexOf(target);
242
+ if (index >= 0) {
243
+ indexSuffix = `[${index}]`;
244
+ }
245
+ }
246
+ } catch {
247
+ // ignore
248
+ }
249
+
250
+ bizId = `${routeKey}|${tagName}.${primaryClass}${indexSuffix}|${safeLabel}`;
251
+ }
252
+
253
+ const targetInfo = {
254
+ tagName: target.tagName,
255
+ className: target.className,
256
+ id: target.id,
257
+ textContent: target.textContent?.substring(0, 50) || '',
258
+ selector: this.getCSSSelector(target)
259
+ };
260
+
261
+ this.trackAction('click', {
262
+ type: 'click',
263
+ bizId,
264
+ x: event.clientX,
265
+ y: event.clientY,
266
+ target: targetInfo,
267
+ page: {
268
+ url: window.location.href,
269
+ title: document.title,
270
+ path: window.location.pathname
271
+ },
272
+ timestamp: Date.now()
273
+ });
274
+ } catch (error) {
275
+ console.warn('Failed to track click event:', error);
276
+ }
277
+ },
278
+ true
279
+ );
280
+
281
+ document.addEventListener('visibilitychange', () => {});
282
+
283
+ if (mitoConfig.debug) {
284
+ console.log('User interaction tracking configured');
285
+ }
286
+ }
287
+
288
+ private getCSSSelector(element: Element | null): string {
289
+ if (!element || element.nodeType !== Node.ELEMENT_NODE) {
290
+ return '';
291
+ }
292
+
293
+ const path: string[] = [];
294
+ let current: Element | null = element;
295
+
296
+ while (current && current.nodeType === Node.ELEMENT_NODE) {
297
+ let selector = current.tagName.toLowerCase();
298
+
299
+ if (current.id) {
300
+ selector += `#${current.id}`;
301
+ path.unshift(selector);
302
+ break;
303
+ }
304
+
305
+ if ((current as HTMLElement).className) {
306
+ const classes = (current as HTMLElement).className
307
+ .split(' ')
308
+ .filter((c) => c.trim());
309
+ if (classes.length > 0) {
310
+ selector += `.${classes[0]}`;
311
+ }
312
+ }
313
+
314
+ path.unshift(selector);
315
+ current = current.parentElement;
316
+
317
+ if (path.length > 5) {
318
+ break;
319
+ }
320
+ }
321
+
322
+ return path.join(' > ');
323
+ }
324
+
325
+ trackEvent(eventName: string, eventData: Record<string, any> = {}): void {
326
+ if (!this.checkRUMAvailable()) {
327
+ return;
328
+ }
329
+
330
+ try {
331
+ this.adapter.trackEvent({
332
+ name: eventName,
333
+ ...eventData
334
+ });
335
+ } catch (error) {
336
+ console.error('Failed to track event:', error);
337
+ }
338
+ }
339
+
340
+ trackPerformance(performanceData: Record<string, any>): void {
341
+ if (!this.checkRUMAvailable()) {
342
+ return;
343
+ }
344
+
345
+ try {
346
+ this.adapter.trackEvent({
347
+ name: 'performance',
348
+ type: 'performance',
349
+ ...performanceData
350
+ });
351
+ } catch (error) {
352
+ console.error('Failed to track performance:', error);
353
+ }
354
+ }
355
+
356
+ trackAction(action: string, actionData: Record<string, any> = {}): void {
357
+ if (!this.checkRUMAvailable()) {
358
+ return;
359
+ }
360
+
361
+ try {
362
+ this.adapter.trackEvent({
363
+ name: 'user_action',
364
+ action,
365
+ ...actionData
366
+ });
367
+ } catch (error) {
368
+ console.error('Failed to track action:', error);
369
+ }
370
+ }
371
+
372
+ trackMetric(
373
+ metricName: string,
374
+ value: number,
375
+ dimensions: Record<string, any> = {}
376
+ ): void {
377
+ if (!this.checkRUMAvailable()) {
378
+ return;
379
+ }
380
+
381
+ try {
382
+ this.adapter.trackEvent({
383
+ name: 'metric',
384
+ metricName,
385
+ value,
386
+ dimensions
387
+ });
388
+ } catch (error) {
389
+ console.error('Failed to track metric:', error);
390
+ }
391
+ }
392
+
393
+ getBreadcrumbs(): any[] {
394
+ if (!this.checkRUMAvailable()) {
395
+ return [];
396
+ }
397
+
398
+ try {
399
+ return this.adapter.getBreadcrumbs();
400
+ } catch (error) {
401
+ console.error('Failed to get breadcrumbs:', error);
402
+ return [];
403
+ }
404
+ }
405
+
406
+ enable(): void {
407
+ this.isEnabled = true;
408
+ if (mitoConfig.debug) {
409
+ console.log('RUM system enabled');
410
+ }
411
+ }
412
+
413
+ disable(): void {
414
+ this.isEnabled = false;
415
+ if (mitoConfig.debug) {
416
+ console.log('RUM system disabled');
417
+ }
418
+ }
419
+
420
+ private checkRUMAvailable(): boolean {
421
+ return this.isEnabled && this.isInitialized && !!this.adapter;
422
+ }
423
+
424
+ getConfig(): Record<string, any> {
425
+ return {
426
+ isInitialized: this.isInitialized,
427
+ isEnabled: this.isEnabled,
428
+ environment: mitoConfig.environment,
429
+ debug: mitoConfig.debug,
430
+ sls: {
431
+ enabled: slsConfig.enabled,
432
+ configured: true,
433
+ project: slsConfig.project,
434
+ logstore: slsConfig.logstore
435
+ }
436
+ };
437
+ }
438
+
439
+ destroy(): void {
440
+ try {
441
+ if (this.adapter) {
442
+ this.adapter.destroy();
443
+ this.adapter = null;
444
+ }
445
+
446
+ this.isInitialized = false;
447
+ this.context = {} as RUMManagerContext;
448
+ if (mitoConfig.debug) {
449
+ console.log('RUM system destroyed');
450
+ }
451
+ } catch (error) {
452
+ console.error('Failed to destroy RUM system:', error);
453
+ }
454
+ }
455
+ }
@@ -0,0 +1,56 @@
1
+ import {
2
+ RUMManagerVue2,
3
+ type RUMManagerContext
4
+ } from './RUMManager.vue2';
5
+ import type { MitoSLSAdapterOptions } from '../core/MitoSLSAdapter';
6
+
7
+ const rumManagerVue2 = new RUMManagerVue2();
8
+
9
+ export const initRUMSystem = (
10
+ context: RUMManagerContext,
11
+ options: Partial<MitoSLSAdapterOptions> = {}
12
+ ) => rumManagerVue2.init(context, options);
13
+
14
+ export const trackEvent = (
15
+ eventName: string,
16
+ eventData?: Record<string, any>
17
+ ) => rumManagerVue2.trackEvent(eventName, eventData || {});
18
+
19
+ export const trackPerformance = (performanceData: Record<string, any>) =>
20
+ rumManagerVue2.trackPerformance(performanceData);
21
+
22
+ export const trackAction = (
23
+ action: string,
24
+ actionData?: Record<string, any>
25
+ ) => rumManagerVue2.trackAction(action, actionData || {});
26
+
27
+ export const trackMetric = (
28
+ metricName: string,
29
+ value: number,
30
+ dimensions?: Record<string, any>
31
+ ) => rumManagerVue2.trackMetric(metricName, value, dimensions || {});
32
+
33
+ export const getBreadcrumbs = () => rumManagerVue2.getBreadcrumbs();
34
+
35
+ export const enableRUM = () => rumManagerVue2.enable();
36
+ export const disableRUM = () => rumManagerVue2.disable();
37
+ export const getRUMConfig = () => rumManagerVue2.getConfig();
38
+ export const destroyRUM = () => rumManagerVue2.destroy();
39
+ export const getRUMManager = () => rumManagerVue2;
40
+
41
+ export const RUMPluginVue2 = {
42
+ install(Vue: any) {
43
+ Vue.prototype.$rum = {
44
+ trackEvent,
45
+ trackPerformance,
46
+ trackAction,
47
+ trackMetric,
48
+ getBreadcrumbs,
49
+ enable: enableRUM,
50
+ disable: disableRUM,
51
+ getConfig: getRUMConfig
52
+ };
53
+
54
+ (Vue as any).$rumManager = rumManagerVue2;
55
+ }
56
+ };
@@ -0,0 +1,56 @@
1
+ import type { TransformContext } from '../core/config';
2
+ import type { MitoSLSAdapterOptions } from '../core/MitoSLSAdapter';
3
+ import { RUMManagerVue2 } from '../vue2/RUMManager.vue2';
4
+
5
+ export interface RUMManagerContextVue3 extends TransformContext {}
6
+
7
+ export class RUMManagerVue3 {
8
+ private inner = new RUMManagerVue2();
9
+
10
+ init(
11
+ context: RUMManagerContextVue3,
12
+ options: Partial<MitoSLSAdapterOptions> = {}
13
+ ): Promise<void> {
14
+ return this.inner.init(context as any, options);
15
+ }
16
+
17
+ trackEvent(eventName: string, eventData: Record<string, any> = {}): void {
18
+ this.inner.trackEvent(eventName, eventData);
19
+ }
20
+
21
+ trackPerformance(performanceData: Record<string, any>): void {
22
+ this.inner.trackPerformance(performanceData);
23
+ }
24
+
25
+ trackAction(action: string, actionData: Record<string, any> = {}): void {
26
+ this.inner.trackAction(action, actionData);
27
+ }
28
+
29
+ trackMetric(
30
+ metricName: string,
31
+ value: number,
32
+ dimensions: Record<string, any> = {}
33
+ ): void {
34
+ this.inner.trackMetric(metricName, value, dimensions);
35
+ }
36
+
37
+ getBreadcrumbs(): any[] {
38
+ return this.inner.getBreadcrumbs();
39
+ }
40
+
41
+ enable(): void {
42
+ this.inner.enable();
43
+ }
44
+
45
+ disable(): void {
46
+ this.inner.disable();
47
+ }
48
+
49
+ getConfig(): Record<string, any> {
50
+ return this.inner.getConfig();
51
+ }
52
+
53
+ destroy(): void {
54
+ this.inner.destroy();
55
+ }
56
+ }
@@ -0,0 +1,40 @@
1
+ import type { App } from 'vue';
2
+ import { inject } from 'vue';
3
+ import type { MitoSLSAdapterOptions } from '../core/MitoSLSAdapter';
4
+ import { RUMManagerVue3 } from './RUMManager.vue3';
5
+
6
+ export interface RUMVue3PluginOptions {
7
+ store?: any;
8
+ router?: any;
9
+ coreOptions?: Partial<MitoSLSAdapterOptions>;
10
+ }
11
+
12
+ export function createRUMPluginVue3(options: RUMVue3PluginOptions) {
13
+ const manager = new RUMManagerVue3();
14
+
15
+ return {
16
+ install(app: App) {
17
+ manager.init(
18
+ { store: options.store, router: options.router },
19
+ options.coreOptions || {}
20
+ );
21
+
22
+ (app.config.globalProperties as any).$rum = {
23
+ trackEvent: manager.trackEvent.bind(manager),
24
+ trackPerformance: manager.trackPerformance.bind(manager),
25
+ trackAction: manager.trackAction.bind(manager),
26
+ trackMetric: manager.trackMetric.bind(manager),
27
+ getBreadcrumbs: manager.getBreadcrumbs.bind(manager),
28
+ enable: manager.enable.bind(manager),
29
+ disable: manager.disable.bind(manager),
30
+ getConfig: manager.getConfig.bind(manager)
31
+ };
32
+
33
+ app.provide('rum', manager);
34
+ }
35
+ };
36
+ }
37
+
38
+ export function useRUM(): RUMManagerVue3 | undefined {
39
+ return inject<RUMManagerVue3>('rum');
40
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2019",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Node",
6
+ "strict": true,
7
+ "declaration": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "outDir": "dist",
12
+ "rootDir": "src",
13
+ "resolveJsonModule": true
14
+ },
15
+ "include": ["src"]
16
+ }