@mtn-ui-z/monitor 0.0.1

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/dist/index.js ADDED
@@ -0,0 +1,962 @@
1
+ var u = Object.defineProperty;
2
+ var m = (a, t, e) => t in a ? u(a, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : a[t] = e;
3
+ var i = (a, t, e) => m(a, typeof t != "symbol" ? t + "" : t, e);
4
+ class f {
5
+ /**
6
+ * 构造函数
7
+ * @param options - 监控配置
8
+ * @param reportFn - 上报函数
9
+ */
10
+ constructor(t, e) {
11
+ /** 监控配置选项 */
12
+ i(this, "options");
13
+ /** 数据上报函数 */
14
+ i(this, "reportFn");
15
+ /** 绑定的事件处理器 - JS 错误 */
16
+ i(this, "boundErrorHandler");
17
+ /** 绑定的事件处理器 - Promise 未捕获异常 */
18
+ i(this, "boundPromiseHandler");
19
+ /** 绑定的事件处理器 - 资源加载错误 */
20
+ i(this, "boundResourceHandler");
21
+ /** Vue 错误处理器 */
22
+ i(this, "vueErrorHandler");
23
+ this.options = t, this.reportFn = e, this.boundErrorHandler = this.handleError.bind(this), this.boundPromiseHandler = this.handlePromiseReject.bind(this), this.boundResourceHandler = this.handleResourceError.bind(this), this.vueErrorHandler = this.handleVueError.bind(this);
24
+ }
25
+ /**
26
+ * 启动错误监控
27
+ * 添加全局事件监听器
28
+ */
29
+ start() {
30
+ this.options.errorEnabled && (window.addEventListener("error", this.boundErrorHandler, !0), window.addEventListener("unhandledrejection", this.boundPromiseHandler, !0), window.addEventListener("error", this.boundResourceHandler, !0));
31
+ }
32
+ /**
33
+ * 停止错误监控
34
+ * 移除所有事件监听器
35
+ */
36
+ stop() {
37
+ window.removeEventListener("error", this.boundErrorHandler, !0), window.removeEventListener("unhandledrejection", this.boundPromiseHandler, !0), window.removeEventListener("error", this.boundResourceHandler, !0);
38
+ }
39
+ /**
40
+ * 创建 Vue 错误处理器
41
+ * 用于设置到 app.config.errorHandler
42
+ * @param customHandler - 用户自定义的处理函数(可选),会在监控上报后调用
43
+ * @returns Vue 错误处理函数
44
+ */
45
+ createVueErrorHandler(t) {
46
+ return (e, o, r) => {
47
+ this.handleVueError(e, o, r), t && t(e, o, r);
48
+ };
49
+ }
50
+ /**
51
+ * 注册 Vue 错误处理器(兼容旧版)
52
+ * @param handler - 用户自定义的 Vue 错误处理函数
53
+ * @returns Vue 错误处理器函数
54
+ * @deprecated 请使用 createVueErrorHandler 代替
55
+ */
56
+ registerVueHandler(t) {
57
+ return this.vueErrorHandler = (e, o, r) => {
58
+ this.handleVueError(e, o, r), t(e, o, r);
59
+ }, this.vueErrorHandler;
60
+ }
61
+ /**
62
+ * 处理 JS 运行时错误
63
+ * @param event - ErrorEvent 事件对象
64
+ */
65
+ handleError(t) {
66
+ var o;
67
+ if (t.message === "ResizeObserver loop limit exceeded" || t.message === "ResizeObserver loop completed with undelivered notifications")
68
+ return;
69
+ const e = {
70
+ type: "error",
71
+ errorType: "js",
72
+ message: t.message,
73
+ stack: (o = t.error) == null ? void 0 : o.stack,
74
+ url: t.filename,
75
+ line: t.lineno,
76
+ column: t.colno,
77
+ timestamp: Date.now()
78
+ };
79
+ this.reportFn(e);
80
+ }
81
+ /**
82
+ * 处理 Promise 未捕获异常
83
+ * @param event - PromiseRejectionEvent 事件对象
84
+ */
85
+ handlePromiseReject(t) {
86
+ var o, r;
87
+ const e = {
88
+ type: "error",
89
+ errorType: "promise",
90
+ message: ((o = t.reason) == null ? void 0 : o.message) || String(t.reason),
91
+ stack: (r = t.reason) == null ? void 0 : r.stack,
92
+ url: window.location.href,
93
+ timestamp: Date.now()
94
+ };
95
+ this.reportFn(e);
96
+ }
97
+ /**
98
+ * 处理资源加载错误
99
+ * @param event - 事件对象
100
+ */
101
+ handleResourceError(t) {
102
+ const e = t.target;
103
+ if (!e || e.tagName !== "SCRIPT" && e.tagName !== "LINK" && e.tagName !== "IMG")
104
+ return;
105
+ const o = {
106
+ type: "error",
107
+ errorType: "resource",
108
+ message: `资源加载失败: ${e.src || e.src || e.tagName}`,
109
+ url: window.location.href,
110
+ timestamp: Date.now()
111
+ };
112
+ this.reportFn(o);
113
+ }
114
+ /**
115
+ * 处理 Vue 组件错误
116
+ * @param err - Vue 错误对象
117
+ * @param instance - 触发错误的组件实例
118
+ * @param info - 错误信息字符串
119
+ */
120
+ handleVueError(t, e, o) {
121
+ const r = {
122
+ type: "error",
123
+ errorType: "vue",
124
+ message: t.message,
125
+ stack: t.stack,
126
+ url: `${window.location.href} (${o})`,
127
+ timestamp: Date.now()
128
+ };
129
+ this.reportFn(r);
130
+ }
131
+ }
132
+ class v {
133
+ /**
134
+ * 构造函数
135
+ * @param options - 监控配置
136
+ * @param reportFn - 上报函数
137
+ */
138
+ constructor(t, e) {
139
+ /** 监控配置选项 */
140
+ i(this, "options");
141
+ /** 数据上报函数 */
142
+ i(this, "reportFn");
143
+ /** PerformanceObserver 实例数组,用于停止时清理 */
144
+ i(this, "observers", []);
145
+ /** load 事件监听器引用 */
146
+ i(this, "loadHandler", null);
147
+ /** CLS 上报定时器引用 */
148
+ i(this, "clsTimeoutId", null);
149
+ this.options = t, this.reportFn = e;
150
+ }
151
+ /**
152
+ * 启动性能监控
153
+ * 采集各类性能指标
154
+ */
155
+ start() {
156
+ this.options.performanceEnabled && (document.readyState === "complete" ? this.reportPagePerformance() : (this.loadHandler = () => {
157
+ setTimeout(() => this.reportPagePerformance(), 0);
158
+ }, window.addEventListener("load", this.loadHandler)), this.observeResources(), this.observeFCP(), this.observeLCP(), this.observeCLS(), this.observeFID());
159
+ }
160
+ /**
161
+ * 停止性能监控
162
+ * 清理所有 observer 和定时器
163
+ */
164
+ stop() {
165
+ this.observers.forEach((t) => {
166
+ try {
167
+ t.disconnect();
168
+ } catch {
169
+ }
170
+ }), this.observers = [], this.loadHandler && (window.removeEventListener("load", this.loadHandler), this.loadHandler = null), this.clsTimeoutId && (clearTimeout(this.clsTimeoutId), this.clsTimeoutId = null);
171
+ }
172
+ /**
173
+ * 上报页面基础性能指标
174
+ * 使用 performance.getEntriesByType('navigation') 获取页面加载各阶段耗时
175
+ */
176
+ reportPagePerformance() {
177
+ const t = performance.getEntriesByType("navigation");
178
+ if (t && t.length > 0) {
179
+ const r = t[0];
180
+ [
181
+ // 页面完整加载时间
182
+ { metric: "page", value: r.loadEventEnd - r.startTime },
183
+ // DNS 查询时间
184
+ { metric: "dns", value: r.domainLookupEnd - r.domainLookupStart },
185
+ // TCP 连接时间
186
+ { metric: "tcp", value: r.connectEnd - r.connectStart },
187
+ // TTFB (Time To First Byte) - 首字节时间
188
+ { metric: "ttfb", value: r.responseStart - r.startTime },
189
+ // 内容下载时间
190
+ { metric: "download", value: r.responseEnd - r.responseStart }
191
+ ].forEach(({ metric: s, value: l }) => {
192
+ l >= 0 && this.reportFn({
193
+ type: "performance",
194
+ metric: s,
195
+ value: l,
196
+ url: window.location.href,
197
+ timestamp: Date.now()
198
+ });
199
+ });
200
+ } else {
201
+ const r = performance.timing;
202
+ if (!r) return;
203
+ [
204
+ // 页面完整加载时间
205
+ { metric: "page", value: r.loadEventEnd - r.navigationStart },
206
+ // DNS 查询时间
207
+ { metric: "dns", value: r.domainLookupEnd - r.domainLookupStart },
208
+ // TCP 连接时间
209
+ { metric: "tcp", value: r.connectEnd - r.connectStart },
210
+ // TTFB (Time To First Byte) - 首字节时间
211
+ { metric: "ttfb", value: r.responseStart - r.navigationStart }
212
+ ].forEach(({ metric: s, value: l }) => {
213
+ l >= 0 && this.reportFn({
214
+ type: "performance",
215
+ metric: s,
216
+ value: l,
217
+ url: window.location.href,
218
+ timestamp: Date.now()
219
+ });
220
+ });
221
+ }
222
+ const o = performance.getEntriesByType("paint").find((r) => r.name === "first-paint");
223
+ o && this.reportFn({
224
+ type: "performance",
225
+ metric: "fp",
226
+ value: o.startTime,
227
+ url: window.location.href,
228
+ timestamp: Date.now()
229
+ });
230
+ }
231
+ /**
232
+ * 监听资源加载性能
233
+ * 使用 PerformanceObserver 监听所有资源请求
234
+ */
235
+ observeResources() {
236
+ try {
237
+ const t = new PerformanceObserver((e) => {
238
+ e.getEntries().forEach((o) => {
239
+ const r = o;
240
+ r.duration > 100 && this.reportFn({
241
+ type: "performance",
242
+ metric: "resource",
243
+ value: r.duration,
244
+ url: window.location.href,
245
+ timestamp: Date.now()
246
+ });
247
+ });
248
+ });
249
+ t.observe({ entryTypes: ["resource"] }), this.observers.push(t);
250
+ } catch {
251
+ }
252
+ }
253
+ /**
254
+ * 监听 First Contentful Paint (FCP)
255
+ * 首次内容绘制时间
256
+ */
257
+ observeFCP() {
258
+ try {
259
+ const t = new PerformanceObserver((e) => {
260
+ const o = e.getEntries(), r = o[o.length - 1];
261
+ this.reportFn({
262
+ type: "performance",
263
+ metric: "fcp",
264
+ value: r.startTime,
265
+ url: window.location.href,
266
+ timestamp: Date.now()
267
+ });
268
+ });
269
+ t.observe({ entryTypes: ["paint"] }), this.observers.push(t);
270
+ } catch {
271
+ }
272
+ }
273
+ /**
274
+ * 监听 Largest Contentful Paint (LCP)
275
+ * 最大内容绘制时间,关键用户体验指标
276
+ */
277
+ observeLCP() {
278
+ try {
279
+ const t = new PerformanceObserver((e) => {
280
+ const o = e.getEntries(), r = o[o.length - 1];
281
+ this.reportFn({
282
+ type: "performance",
283
+ metric: "lcp",
284
+ value: r.startTime,
285
+ url: window.location.href,
286
+ timestamp: Date.now()
287
+ });
288
+ });
289
+ t.observe({ entryTypes: ["largest-contentful-paint"] }), this.observers.push(t);
290
+ } catch {
291
+ }
292
+ }
293
+ /**
294
+ * 监听 Cumulative Layout Shift (CLS)
295
+ * 累计布局偏移,衡量页面稳定性
296
+ */
297
+ observeCLS() {
298
+ let t = 0;
299
+ try {
300
+ const e = new PerformanceObserver((o) => {
301
+ for (const r of o.getEntries()) {
302
+ const n = r;
303
+ n.hadRecentInput || (t += n.value);
304
+ }
305
+ });
306
+ e.observe({ entryTypes: ["layout-shift"] }), this.observers.push(e), this.clsTimeoutId = setTimeout(() => {
307
+ this.reportFn({
308
+ type: "performance",
309
+ metric: "cls",
310
+ value: t,
311
+ url: window.location.href,
312
+ timestamp: Date.now()
313
+ });
314
+ }, 3e3);
315
+ } catch {
316
+ }
317
+ }
318
+ /**
319
+ * 监听 First Input Delay (FID)
320
+ * 首次输入延迟,衡量页面响应性
321
+ */
322
+ observeFID() {
323
+ try {
324
+ const t = new PerformanceObserver((e) => {
325
+ const r = e.getEntries()[0];
326
+ this.reportFn({
327
+ type: "performance",
328
+ metric: "fid",
329
+ value: r.processingStart - r.startTime,
330
+ url: window.location.href,
331
+ timestamp: Date.now()
332
+ });
333
+ });
334
+ t.observe({ entryTypes: ["first-input"] }), this.observers.push(t);
335
+ } catch {
336
+ }
337
+ }
338
+ }
339
+ class w {
340
+ /**
341
+ * 构造函数
342
+ * @param options - 监控配置
343
+ * @param reportFn - 上报函数
344
+ */
345
+ constructor(t, e) {
346
+ /** 监控配置选项 */
347
+ i(this, "options");
348
+ /** 数据上报函数 */
349
+ i(this, "reportFn");
350
+ /** 当前用户 ID */
351
+ i(this, "userId");
352
+ /** 原始的 pushState 方法 */
353
+ i(this, "originalPushState", null);
354
+ /** 原始的 replaceState 方法 */
355
+ i(this, "originalReplaceState", null);
356
+ /** popstate 事件回调 */
357
+ i(this, "popstateHandler", null);
358
+ /** 是否已启动 */
359
+ i(this, "isStarted", !1);
360
+ this.options = t, this.reportFn = e;
361
+ }
362
+ /**
363
+ * 启动页面访问监控
364
+ * 首次访问立即上报,并监听路由变化
365
+ */
366
+ start() {
367
+ this.options.pvEnabled && (this.isStarted || (this.report(), this.observeRouter(), this.isStarted = !0));
368
+ }
369
+ /**
370
+ * 停止页面访问监控
371
+ * 恢复原始方法,移除事件监听
372
+ */
373
+ stop() {
374
+ this.originalPushState && (window.history.pushState = this.originalPushState, this.originalPushState = null), this.originalReplaceState && (window.history.replaceState = this.originalReplaceState, this.originalReplaceState = null), this.popstateHandler && (window.removeEventListener("popstate", this.popstateHandler), this.popstateHandler = null), this.isStarted = !1;
375
+ }
376
+ /**
377
+ * 手动触发一次 PV 上报
378
+ * 可用于路由切换后手动上报
379
+ */
380
+ report() {
381
+ const t = {
382
+ type: "pv",
383
+ url: window.location.href,
384
+ referrer: document.referrer,
385
+ userId: this.userId,
386
+ timestamp: Date.now()
387
+ };
388
+ this.reportFn(t);
389
+ }
390
+ /**
391
+ * 设置用户 ID
392
+ * @param userId - 用户唯一标识
393
+ */
394
+ setUserId(t) {
395
+ this.userId = t;
396
+ }
397
+ /**
398
+ * 监听路由变化
399
+ * 支持浏览器前进后退、pushState
400
+ * 注意:仅在 pushState 和 popstate 时上报,不在 replaceState 时上报。
401
+ * Vue Router 4 在一次 router.push() 中会先 replaceState 再 pushState,若 replaceState 也上报会导致同一次跳转产生两次 PV。
402
+ */
403
+ observeRouter() {
404
+ this.originalPushState = window.history.pushState.bind(window.history), this.originalReplaceState = window.history.replaceState.bind(window.history), this.popstateHandler = () => {
405
+ this.report();
406
+ }, window.addEventListener("popstate", this.popstateHandler), window.history.pushState = (...t) => {
407
+ this.originalPushState(...t), this.report();
408
+ }, window.history.replaceState = (...t) => {
409
+ this.originalReplaceState(...t);
410
+ };
411
+ }
412
+ }
413
+ class b {
414
+ /**
415
+ * 构造函数
416
+ * @param options - 监控配置
417
+ * @param reportFn - 上报函数
418
+ */
419
+ constructor(t, e) {
420
+ /** 监控配置选项 */
421
+ i(this, "options");
422
+ /** 数据上报函数 */
423
+ i(this, "reportFn");
424
+ /** 原始 fetch 方法 */
425
+ i(this, "originalFetch", null);
426
+ /** 原始 XMLHttpRequest */
427
+ i(this, "originalXhr", null);
428
+ /** 是否已启动 */
429
+ i(this, "isStarted", !1);
430
+ this.options = t, this.reportFn = e;
431
+ }
432
+ /**
433
+ * 启动 API 监控
434
+ */
435
+ start() {
436
+ this.isStarted || this.options.apiEnabled && (this.interceptFetch(), this.interceptXhr(), this.isStarted = !0);
437
+ }
438
+ /**
439
+ * 停止 API 监控
440
+ */
441
+ stop() {
442
+ this.originalFetch && (window.fetch = this.originalFetch, this.originalFetch = null), this.originalXhr && (window.XMLHttpRequest = this.originalXhr, this.originalXhr = null), this.isStarted = !1;
443
+ }
444
+ /**
445
+ * 拦截 fetch 请求
446
+ */
447
+ interceptFetch() {
448
+ this.originalFetch = window.fetch.bind(window), window.fetch = async (...t) => {
449
+ var h;
450
+ const e = Date.now(), o = typeof t[0] == "string" ? t[0] : t[0].url, r = ((h = t[1]) == null ? void 0 : h.method) || "GET";
451
+ let n = null, s = null;
452
+ try {
453
+ n = await this.originalFetch(...t);
454
+ } catch (d) {
455
+ s = d;
456
+ }
457
+ const l = Date.now() - e, c = (n == null ? void 0 : n.status) || 0;
458
+ if (this.reportFn({
459
+ type: "api",
460
+ url: o,
461
+ method: r.toUpperCase(),
462
+ status: c,
463
+ duration: l,
464
+ success: c >= 200 && c < 400 && !s,
465
+ error: s ? s.message : c >= 400 ? `HTTP ${c}` : void 0,
466
+ timestamp: Date.now()
467
+ }), s)
468
+ throw s;
469
+ return n;
470
+ };
471
+ }
472
+ /**
473
+ * 拦截 XMLHttpRequest
474
+ */
475
+ interceptXhr() {
476
+ const t = window.XMLHttpRequest;
477
+ this.originalXhr = t, window.XMLHttpRequest = class extends t {
478
+ constructor() {
479
+ super(...arguments);
480
+ i(this, "_startTime", 0);
481
+ i(this, "_url", "");
482
+ i(this, "_method", "GET");
483
+ }
484
+ open(r, n, s = !0, l, c) {
485
+ return this._method = r.toUpperCase(), this._url = n, super.open(r, n, s, l ?? void 0, c ?? void 0);
486
+ }
487
+ send(r) {
488
+ return this._startTime = Date.now(), this.addEventListener("loadend", () => {
489
+ const n = Date.now() - this._startTime, s = this.status;
490
+ e({
491
+ type: "api",
492
+ url: this._url,
493
+ method: this._method,
494
+ status: s,
495
+ duration: n,
496
+ success: s >= 200 && s < 400,
497
+ error: s >= 400 ? `HTTP ${s}` : void 0,
498
+ timestamp: Date.now()
499
+ });
500
+ }), super.send(r);
501
+ }
502
+ };
503
+ const e = (o) => {
504
+ this.reportFn(o);
505
+ };
506
+ }
507
+ }
508
+ class y {
509
+ /**
510
+ * 构造函数
511
+ * @param options - 监控配置
512
+ * @param reportFn - 上报函数
513
+ */
514
+ constructor(t, e) {
515
+ /** 监控配置选项 */
516
+ i(this, "options");
517
+ /** 数据上报函数 */
518
+ i(this, "reportFn");
519
+ /** 是否已启动 */
520
+ i(this, "isStarted", !1);
521
+ /** 点击事件处理器(用于 stop 时移除) */
522
+ i(this, "boundClickHandler", null);
523
+ /** 滚动事件处理器(用于 stop 时移除) */
524
+ i(this, "boundScrollHandler", null);
525
+ /** 可见性变化处理器(用于 stop 时移除) */
526
+ i(this, "boundVisibilityHandler", null);
527
+ this.options = t, this.reportFn = e;
528
+ }
529
+ /**
530
+ * 启动用户行为监控
531
+ */
532
+ start() {
533
+ this.isStarted || this.options.behaviorEnabled && (this.observeClick(), this.observeScroll(), this.observeVisibility(), this.isStarted = !0);
534
+ }
535
+ /**
536
+ * 停止用户行为监控
537
+ */
538
+ stop() {
539
+ this.isStarted && (this.boundClickHandler && (document.removeEventListener("click", this.boundClickHandler, !0), this.boundClickHandler = null), this.boundScrollHandler && (window.removeEventListener("scroll", this.boundScrollHandler, { passive: !0 }), this.boundScrollHandler = null), this.boundVisibilityHandler && (document.removeEventListener("visibilitychange", this.boundVisibilityHandler), this.boundVisibilityHandler = null), this.isStarted = !1);
540
+ }
541
+ /**
542
+ * 监听点击事件
543
+ */
544
+ observeClick() {
545
+ this.boundClickHandler = (t) => {
546
+ var l;
547
+ const e = t.target;
548
+ if (!e) return;
549
+ const o = [];
550
+ let r = e;
551
+ for (; r && r !== document.body; ) {
552
+ const c = r.tagName.toLowerCase(), h = r.id ? `#${r.id}` : "", d = r.className ? `.${r.className.split(" ").join(".")}` : "";
553
+ c !== "body" && o.unshift(`${c}${h}${d}`), r = r.parentElement;
554
+ }
555
+ const n = e.getAttribute("data-monitor-id"), s = e.getAttribute("data-monitor-label");
556
+ this.reportFn({
557
+ type: "behavior",
558
+ behaviorType: "click",
559
+ element: e.tagName.toLowerCase(),
560
+ text: ((l = e.textContent) == null ? void 0 : l.slice(0, 50)) || "",
561
+ path: o.join(" > "),
562
+ monitorId: n || void 0,
563
+ monitorLabel: s || void 0,
564
+ x: t.clientX,
565
+ y: t.clientY,
566
+ timestamp: Date.now()
567
+ });
568
+ }, document.addEventListener("click", this.boundClickHandler, !0);
569
+ }
570
+ /**
571
+ * 监听滚动事件
572
+ */
573
+ observeScroll() {
574
+ let t = 0;
575
+ const e = 1e3;
576
+ this.boundScrollHandler = () => {
577
+ const o = Date.now();
578
+ if (o - t < e) return;
579
+ t = o;
580
+ const r = window.scrollY || document.documentElement.scrollTop, n = document.documentElement.scrollHeight, s = document.documentElement.clientHeight, l = n - s, c = l > 0 ? Math.round(r / l * 100) : 0;
581
+ this.reportFn({
582
+ type: "behavior",
583
+ behaviorType: "scroll",
584
+ scrollTop: r,
585
+ scrollPercent: c,
586
+ timestamp: Date.now()
587
+ });
588
+ }, window.addEventListener("scroll", this.boundScrollHandler, { passive: !0 });
589
+ }
590
+ /**
591
+ * 监听页面可见性变化
592
+ */
593
+ observeVisibility() {
594
+ this.boundVisibilityHandler = () => {
595
+ document.visibilityState === "hidden" ? this.reportFn({
596
+ type: "behavior",
597
+ behaviorType: "pageHide",
598
+ timestamp: Date.now()
599
+ }) : document.visibilityState === "visible" && this.reportFn({
600
+ type: "behavior",
601
+ behaviorType: "pageShow",
602
+ timestamp: Date.now()
603
+ });
604
+ }, document.addEventListener("visibilitychange", this.boundVisibilityHandler);
605
+ }
606
+ }
607
+ class g {
608
+ /**
609
+ * 构造函数
610
+ * @param options - 监控配置
611
+ * @param reportFn - 上报函数
612
+ */
613
+ constructor(t, e) {
614
+ /** 监控配置选项 */
615
+ i(this, "options");
616
+ /** 数据上报函数 */
617
+ i(this, "reportFn");
618
+ this.options = t, this.reportFn = e;
619
+ }
620
+ /**
621
+ * 上报自定义事件
622
+ * @param eventName - 事件名称
623
+ * @param eventData - 事件数据
624
+ */
625
+ report(t, e) {
626
+ this.reportFn({
627
+ type: "custom",
628
+ eventName: t,
629
+ data: e,
630
+ url: window.location.href,
631
+ timestamp: Date.now()
632
+ });
633
+ }
634
+ /**
635
+ * 上报页面停留事件
636
+ * @param duration - 停留时长(毫秒)
637
+ */
638
+ reportStay(t) {
639
+ this.reportFn({
640
+ type: "custom",
641
+ eventName: "page_stay",
642
+ data: { duration: t },
643
+ url: window.location.href,
644
+ timestamp: Date.now()
645
+ });
646
+ }
647
+ }
648
+ class S {
649
+ /**
650
+ * 构造函数
651
+ * @param options - 监控配置
652
+ * @param reportFn - 上报函数
653
+ */
654
+ constructor(t, e) {
655
+ /** 监控配置选项 */
656
+ i(this, "options");
657
+ /** 数据上报函数 */
658
+ i(this, "reportFn");
659
+ /** PerformanceObserver 实例 */
660
+ i(this, "observer", null);
661
+ /** 是否已启动 */
662
+ i(this, "isStarted", !1);
663
+ /** 内存信息轮询定时器 */
664
+ i(this, "memoryInterval", null);
665
+ this.options = t, this.reportFn = e;
666
+ }
667
+ /**
668
+ * 启动长任务监控
669
+ */
670
+ start() {
671
+ this.isStarted || this.options.longTaskEnabled && (this.observeLongTask(), this.observeMemory(), this.isStarted = !0);
672
+ }
673
+ /**
674
+ * 停止长任务监控
675
+ */
676
+ stop() {
677
+ if (this.observer) {
678
+ try {
679
+ this.observer.disconnect();
680
+ } catch {
681
+ }
682
+ this.observer = null;
683
+ }
684
+ this.memoryInterval && (clearInterval(this.memoryInterval), this.memoryInterval = null), this.isStarted = !1;
685
+ }
686
+ /**
687
+ * 监控 Long Task
688
+ * Long Task 是指执行时间超过 50ms 的任务
689
+ */
690
+ observeLongTask() {
691
+ try {
692
+ this.observer = new PerformanceObserver((t) => {
693
+ for (const e of t.getEntries()) {
694
+ const o = e;
695
+ this.reportFn({
696
+ type: "longtask",
697
+ taskType: "longTask",
698
+ duration: o.duration,
699
+ attribution: o.attribution ? JSON.stringify(o.attribution) : void 0,
700
+ url: window.location.href,
701
+ timestamp: Date.now()
702
+ });
703
+ }
704
+ }), this.observer.observe({ entryTypes: ["longtask"] });
705
+ } catch {
706
+ }
707
+ }
708
+ /**
709
+ * 监控内存使用情况
710
+ * 兼容新版 Performance.memory 和旧版 performance.msMemoryUsageInfo
711
+ */
712
+ observeMemory() {
713
+ const t = () => {
714
+ if ("memory" in performance) {
715
+ const o = performance.memory;
716
+ return {
717
+ usedJSHeapSize: o.usedJSHeapSize,
718
+ totalJSHeapSize: o.totalJSHeapSize
719
+ };
720
+ }
721
+ if ("msMemoryUsage" in performance) {
722
+ const o = performance.msMemoryUsage;
723
+ return {
724
+ usedJSHeapSize: o.totalJSHeapSize,
725
+ totalJSHeapSize: o.totalJSHeapSize
726
+ };
727
+ }
728
+ return null;
729
+ };
730
+ t() && (this.memoryInterval = setInterval(() => {
731
+ const o = t();
732
+ if (!o) return;
733
+ const r = Math.round(o.usedJSHeapSize / 1024 / 1024), n = Math.round(o.totalJSHeapSize / 1024 / 1024), s = Math.round(o.usedJSHeapSize / o.totalJSHeapSize * 100);
734
+ this.reportFn({
735
+ type: "longtask",
736
+ taskType: "memory",
737
+ usedMemory: r,
738
+ totalMemory: n,
739
+ memoryPercent: s,
740
+ url: window.location.href,
741
+ timestamp: Date.now()
742
+ });
743
+ }, 3e4));
744
+ }
745
+ }
746
+ class E {
747
+ /**
748
+ * 构造函数
749
+ * @param options - 监控配置
750
+ */
751
+ constructor(t) {
752
+ /** 监控配置选项 */
753
+ i(this, "options");
754
+ this.options = t;
755
+ }
756
+ /**
757
+ * 上报监控数据
758
+ * @param data - 监控数据
759
+ */
760
+ report(t) {
761
+ if (!this.isSampled()) return;
762
+ const e = {
763
+ ...t,
764
+ appId: this.options.appId,
765
+ sdkKey: this.options.sdkKey,
766
+ env: this.options.env
767
+ };
768
+ if (this.options.report) {
769
+ this.options.report(e);
770
+ return;
771
+ }
772
+ this.options.reportUrl && this.defaultReport(e);
773
+ }
774
+ /**
775
+ * 判断是否在采样范围内
776
+ * @returns 是否应该上报
777
+ */
778
+ isSampled() {
779
+ const t = this.options.sampleRate ?? 1;
780
+ return Math.random() < t;
781
+ }
782
+ /**
783
+ * 默认上报实现
784
+ * 优先使用 sendBeacon,回退使用 fetch
785
+ * @param data - 监控数据
786
+ */
787
+ defaultReport(t) {
788
+ const e = this.options.reportUrl;
789
+ if (navigator.sendBeacon) {
790
+ const o = new Blob([JSON.stringify(t)], { type: "application/json" });
791
+ navigator.sendBeacon(e, o);
792
+ } else
793
+ fetch(e, {
794
+ method: "POST",
795
+ headers: {
796
+ "Content-Type": "application/json"
797
+ },
798
+ body: JSON.stringify(t),
799
+ // keepalive 确保页面卸载时请求不中断
800
+ keepalive: !0
801
+ }).catch(() => {
802
+ });
803
+ }
804
+ }
805
+ class H {
806
+ constructor() {
807
+ /** 监控配置选项 */
808
+ i(this, "options", {});
809
+ /** 错误监控模块 */
810
+ i(this, "errorMonitor");
811
+ /** 性能监控模块 */
812
+ i(this, "performanceMonitor");
813
+ /** PV 监控模块 */
814
+ i(this, "pvMonitor");
815
+ /** API 监控模块 */
816
+ i(this, "apiMonitor");
817
+ /** 用户行为监控模块 */
818
+ i(this, "behaviorMonitor");
819
+ /** 自定义事件监控模块 */
820
+ i(this, "customMonitor");
821
+ /** 长任务监控模块 */
822
+ i(this, "longTaskMonitor");
823
+ /** 上报管理模块 */
824
+ i(this, "reportManager");
825
+ /** 当前用户 ID */
826
+ i(this, "userId");
827
+ /** 是否已初始化 */
828
+ i(this, "isInited", !1);
829
+ }
830
+ /**
831
+ * 初始化监控 SDK
832
+ * @param options - 监控配置选项
833
+ */
834
+ init(t) {
835
+ if (this.isInited) {
836
+ console.warn("[mtn-monitor] Already initialized");
837
+ return;
838
+ }
839
+ this.options = {
840
+ errorEnabled: !0,
841
+ performanceEnabled: !0,
842
+ pvEnabled: !0,
843
+ apiEnabled: !0,
844
+ behaviorEnabled: !1,
845
+ // 默认关闭,避免过多上报
846
+ longTaskEnabled: !1,
847
+ // 默认关闭,因为有性能开销
848
+ sampleRate: 1,
849
+ env: "production",
850
+ ...t
851
+ }, this.reportManager = new E(this.options);
852
+ const e = (o) => {
853
+ var r;
854
+ (r = this.reportManager) == null || r.report(o);
855
+ };
856
+ this.errorMonitor = new f(this.options, e), this.performanceMonitor = new v(this.options, e), this.pvMonitor = new w(this.options, e), this.apiMonitor = new b(this.options, e), this.behaviorMonitor = new y(this.options, e), this.customMonitor = new g(this.options, e), this.longTaskMonitor = new S(this.options, e), this.errorMonitor.start(), this.performanceMonitor.start(), this.pvMonitor.start(), this.apiMonitor.start(), this.behaviorMonitor.start(), this.longTaskMonitor.start(), this.isInited = !0;
857
+ }
858
+ /**
859
+ * 手动上报错误
860
+ * @param error - Error 对象
861
+ * @param errorType - 错误类型
862
+ */
863
+ reportError(t, e = "js") {
864
+ if (!this.reportManager) {
865
+ console.warn("[mtn-monitor] Please call init() first");
866
+ return;
867
+ }
868
+ this.reportManager.report({
869
+ type: "error",
870
+ errorType: e,
871
+ message: t.message,
872
+ stack: t.stack,
873
+ url: window.location.href,
874
+ userId: this.userId,
875
+ timestamp: Date.now()
876
+ });
877
+ }
878
+ /**
879
+ * 手动上报性能数据
880
+ * @param metric - 性能指标类型
881
+ * @param value - 指标值(毫秒)
882
+ */
883
+ reportPerformance(t, e) {
884
+ if (!this.reportManager) {
885
+ console.warn("[mtn-monitor] Please call init() first");
886
+ return;
887
+ }
888
+ this.reportManager.report({
889
+ type: "performance",
890
+ metric: t,
891
+ value: e,
892
+ url: window.location.href,
893
+ timestamp: Date.now()
894
+ });
895
+ }
896
+ /**
897
+ * 手动上报页面访问(PV)
898
+ */
899
+ reportPv() {
900
+ if (!this.pvMonitor) {
901
+ console.warn("[mtn-monitor] Please call init() first");
902
+ return;
903
+ }
904
+ this.pvMonitor.report();
905
+ }
906
+ /**
907
+ * 手动上报自定义事件
908
+ * @param eventName - 事件名称
909
+ * @param eventData - 事件数据
910
+ */
911
+ reportCustomEvent(t, e) {
912
+ if (!this.customMonitor) {
913
+ console.warn("[mtn-monitor] Please call init() first");
914
+ return;
915
+ }
916
+ this.customMonitor.report(t, e);
917
+ }
918
+ /**
919
+ * 设置用户 ID
920
+ * 用于关联用户行为
921
+ * @param userId - 用户唯一标识
922
+ */
923
+ setUserId(t) {
924
+ var e;
925
+ this.userId = t, (e = this.pvMonitor) == null || e.setUserId(t);
926
+ }
927
+ /**
928
+ * 销毁监控实例
929
+ * 停止所有监控,释放资源
930
+ */
931
+ destroy() {
932
+ var t, e, o, r, n, s;
933
+ (t = this.errorMonitor) == null || t.stop(), (e = this.performanceMonitor) == null || e.stop(), (o = this.pvMonitor) == null || o.stop(), (r = this.apiMonitor) == null || r.stop(), (n = this.behaviorMonitor) == null || n.stop(), (s = this.longTaskMonitor) == null || s.stop(), this.isInited = !1;
934
+ }
935
+ }
936
+ const p = new H(), T = {
937
+ /**
938
+ * 安装 Vue 插件
939
+ * @param app - Vue 应用实例
940
+ * @param options - 监控配置选项
941
+ */
942
+ install(a, t) {
943
+ p.init(t), a.config.globalProperties.$monitor = p, a.provide("monitor", p);
944
+ const e = a.config.errorHandler;
945
+ a.config.errorHandler = (o, r, n) => {
946
+ p.reportError(o, "vue"), e && e(o, r, n);
947
+ };
948
+ }
949
+ };
950
+ export {
951
+ b as ApiMonitor,
952
+ y as BehaviorMonitor,
953
+ g as CustomMonitor,
954
+ f as ErrorMonitor,
955
+ S as LongTaskMonitor,
956
+ T as MtnMonitor,
957
+ v as PerformanceMonitor,
958
+ w as PvMonitor,
959
+ E as ReportManager,
960
+ p as default,
961
+ p as monitor
962
+ };