sobey-monitor-sdk 1.1.2 → 1.1.4
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/core/config.d.ts +0 -1
- package/dist/frameworks/react.d.ts +1 -1
- package/dist/frameworks/vue.d.ts +1 -1
- package/dist/index.cjs.js +261 -155
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +27 -19
- package/dist/index.esm.js +261 -155
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +261 -155
- package/dist/index.umd.js.map +1 -1
- package/dist/reporter/index.d.ts +18 -0
- package/package.json +1 -1
package/dist/index.umd.js
CHANGED
|
@@ -51,27 +51,14 @@
|
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
53
|
* 初始化配置
|
|
54
|
-
* @param skipValidation 是否跳过必填校验(用于远程配置已获取 appId/dsn 的场景)
|
|
55
54
|
*/
|
|
56
55
|
init(userConfig, skipValidation = false) {
|
|
57
56
|
if (!skipValidation) {
|
|
58
|
-
// 如果没有 configUrl,则 appId 和 dsn 是必填的
|
|
59
|
-
if (!userConfig.configUrl) {
|
|
60
|
-
if (!userConfig.appId) {
|
|
61
|
-
throw new Error('[Monitor] appId is required when configUrl is not provided');
|
|
62
|
-
}
|
|
63
|
-
if (!userConfig.dsn) {
|
|
64
|
-
throw new Error('[Monitor] dsn is required when configUrl is not provided');
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
// 远程配置模式下,校验合并后的配置
|
|
70
57
|
if (!userConfig.appId) {
|
|
71
|
-
throw new Error('[Monitor] appId is required
|
|
58
|
+
throw new Error('[Monitor] appId is required');
|
|
72
59
|
}
|
|
73
60
|
if (!userConfig.dsn) {
|
|
74
|
-
throw new Error('[Monitor] dsn is required
|
|
61
|
+
throw new Error('[Monitor] dsn is required');
|
|
75
62
|
}
|
|
76
63
|
}
|
|
77
64
|
this.config = this.mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
@@ -295,6 +282,13 @@
|
|
|
295
282
|
doSend(data) {
|
|
296
283
|
const cfg = config.get();
|
|
297
284
|
const dsn = cfg.dsn;
|
|
285
|
+
// dsn 未配置,跳过发送
|
|
286
|
+
if (!dsn) {
|
|
287
|
+
if (cfg.debug) {
|
|
288
|
+
console.warn('[Monitor] dsn not configured, skip sending');
|
|
289
|
+
}
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
298
292
|
const payload = JSON.stringify(data);
|
|
299
293
|
// 优先使用 sendBeacon(更可靠、异步、不阻塞页面)
|
|
300
294
|
if (supportsSendBeacon()) {
|
|
@@ -393,13 +387,63 @@
|
|
|
393
387
|
* 上报器类
|
|
394
388
|
*/
|
|
395
389
|
class Reporter {
|
|
390
|
+
constructor() {
|
|
391
|
+
/** SDK 是否就绪 */
|
|
392
|
+
this.ready = false;
|
|
393
|
+
/** 早期数据缓存队列(SDK 未就绪时缓存) */
|
|
394
|
+
this.earlyBuffer = [];
|
|
395
|
+
/** 最大缓存数量 */
|
|
396
|
+
this.maxEarlyBufferSize = 50;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* 设置 SDK 就绪状态
|
|
400
|
+
*/
|
|
401
|
+
setReady(ready) {
|
|
402
|
+
this.ready = ready;
|
|
403
|
+
// 就绪后发送缓存的早期数据
|
|
404
|
+
if (ready && this.earlyBuffer.length > 0) {
|
|
405
|
+
console.log(`[Monitor] Flushing ${this.earlyBuffer.length} early buffered items`);
|
|
406
|
+
this.flushEarlyBuffer();
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* 发送早期缓存的数据
|
|
411
|
+
*/
|
|
412
|
+
flushEarlyBuffer() {
|
|
413
|
+
while (this.earlyBuffer.length > 0) {
|
|
414
|
+
const item = this.earlyBuffer.shift();
|
|
415
|
+
if (!item)
|
|
416
|
+
continue;
|
|
417
|
+
switch (item.type) {
|
|
418
|
+
case 'error':
|
|
419
|
+
this.reportError(item.data);
|
|
420
|
+
break;
|
|
421
|
+
case 'performance':
|
|
422
|
+
this.reportPerformance(item.data);
|
|
423
|
+
break;
|
|
424
|
+
case 'behavior':
|
|
425
|
+
this.reportBehavior(item.data);
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* 缓存早期数据
|
|
432
|
+
*/
|
|
433
|
+
bufferEarly(type, data) {
|
|
434
|
+
if (this.earlyBuffer.length >= this.maxEarlyBufferSize) {
|
|
435
|
+
// 缓存已满,丢弃最早的数据
|
|
436
|
+
this.earlyBuffer.shift();
|
|
437
|
+
}
|
|
438
|
+
this.earlyBuffer.push({ type, data });
|
|
439
|
+
}
|
|
396
440
|
/**
|
|
397
441
|
* 构建基础数据
|
|
398
442
|
*/
|
|
399
443
|
buildBaseData() {
|
|
400
444
|
const cfg = config.get();
|
|
401
445
|
return {
|
|
402
|
-
appId: cfg.appId,
|
|
446
|
+
appId: cfg.appId || '',
|
|
403
447
|
userId: cfg.user?.userId,
|
|
404
448
|
sessionId: context.getSessionId(),
|
|
405
449
|
pageUrl: getPageUrl(),
|
|
@@ -420,6 +464,11 @@
|
|
|
420
464
|
* 上报错误
|
|
421
465
|
*/
|
|
422
466
|
reportError(data) {
|
|
467
|
+
// SDK 未就绪时缓存数据
|
|
468
|
+
if (!this.ready) {
|
|
469
|
+
this.bufferEarly('error', data);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
423
472
|
if (!this.shouldSample('error'))
|
|
424
473
|
return;
|
|
425
474
|
const cfg = config.get();
|
|
@@ -441,6 +490,11 @@
|
|
|
441
490
|
* 上报性能
|
|
442
491
|
*/
|
|
443
492
|
reportPerformance(data) {
|
|
493
|
+
// SDK 未就绪时缓存数据
|
|
494
|
+
if (!this.ready) {
|
|
495
|
+
this.bufferEarly('performance', data);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
444
498
|
if (!this.shouldSample('performance'))
|
|
445
499
|
return;
|
|
446
500
|
const reportData = {
|
|
@@ -453,6 +507,11 @@
|
|
|
453
507
|
* 上报行为
|
|
454
508
|
*/
|
|
455
509
|
reportBehavior(data) {
|
|
510
|
+
// SDK 未就绪时缓存数据
|
|
511
|
+
if (!this.ready) {
|
|
512
|
+
this.bufferEarly('behavior', data);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
456
515
|
if (!this.shouldSample('behavior'))
|
|
457
516
|
return;
|
|
458
517
|
const reportData = {
|
|
@@ -494,11 +553,14 @@
|
|
|
494
553
|
* 安装 JS 错误监听
|
|
495
554
|
*/
|
|
496
555
|
function installJsErrorHandler() {
|
|
497
|
-
const cfg = config.get();
|
|
498
|
-
if (!cfg.error?.enabled || !cfg.error?.jsError) {
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
556
|
window.addEventListener('error', (event) => {
|
|
557
|
+
// 延迟检查配置(SDK 可能还在加载配置)
|
|
558
|
+
if (config.isInitialized()) {
|
|
559
|
+
const cfg = config.get();
|
|
560
|
+
if (!cfg.error?.enabled || !cfg.error?.jsError) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
502
564
|
// 过滤资源加载错误(由 resourceError 处理)
|
|
503
565
|
if (event.target !== window) {
|
|
504
566
|
return;
|
|
@@ -523,9 +585,6 @@
|
|
|
523
585
|
});
|
|
524
586
|
reporter.reportError(errorData);
|
|
525
587
|
}, true);
|
|
526
|
-
if (cfg.debug) {
|
|
527
|
-
console.log('[Monitor] JS error handler installed');
|
|
528
|
-
}
|
|
529
588
|
}
|
|
530
589
|
|
|
531
590
|
/**
|
|
@@ -535,11 +594,14 @@
|
|
|
535
594
|
* 安装 Promise 错误监听
|
|
536
595
|
*/
|
|
537
596
|
function installPromiseErrorHandler() {
|
|
538
|
-
const cfg = config.get();
|
|
539
|
-
if (!cfg.error?.enabled || !cfg.error?.promiseError) {
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
597
|
window.addEventListener('unhandledrejection', (event) => {
|
|
598
|
+
// 延迟检查配置(SDK 可能还在加载配置)
|
|
599
|
+
if (config.isInitialized()) {
|
|
600
|
+
const cfg = config.get();
|
|
601
|
+
if (!cfg.error?.enabled || !cfg.error?.promiseError) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
543
605
|
let message = 'Unhandled Promise rejection';
|
|
544
606
|
let stack;
|
|
545
607
|
const reason = event.reason;
|
|
@@ -568,9 +630,6 @@
|
|
|
568
630
|
});
|
|
569
631
|
reporter.reportError(errorData);
|
|
570
632
|
});
|
|
571
|
-
if (cfg.debug) {
|
|
572
|
-
console.log('[Monitor] Promise error handler installed');
|
|
573
|
-
}
|
|
574
633
|
}
|
|
575
634
|
|
|
576
635
|
/**
|
|
@@ -580,11 +639,14 @@
|
|
|
580
639
|
* 安装资源错误监听
|
|
581
640
|
*/
|
|
582
641
|
function installResourceErrorHandler() {
|
|
583
|
-
const cfg = config.get();
|
|
584
|
-
if (!cfg.error?.enabled || !cfg.error?.resourceError) {
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
642
|
window.addEventListener('error', (event) => {
|
|
643
|
+
// 延迟检查配置(SDK 可能还在加载配置)
|
|
644
|
+
if (config.isInitialized()) {
|
|
645
|
+
const cfg = config.get();
|
|
646
|
+
if (!cfg.error?.enabled || !cfg.error?.resourceError) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
588
650
|
const target = event.target;
|
|
589
651
|
// 只处理资源加载错误(target 不是 window)
|
|
590
652
|
if (!target || target === window || !(target instanceof HTMLElement)) {
|
|
@@ -618,9 +680,6 @@
|
|
|
618
680
|
});
|
|
619
681
|
reporter.reportError(errorData);
|
|
620
682
|
}, true); // 使用捕获阶段
|
|
621
|
-
if (cfg.debug) {
|
|
622
|
-
console.log('[Monitor] Resource error handler installed');
|
|
623
|
-
}
|
|
624
683
|
}
|
|
625
684
|
|
|
626
685
|
/**
|
|
@@ -635,28 +694,32 @@
|
|
|
635
694
|
* 安装 HTTP 错误拦截
|
|
636
695
|
*/
|
|
637
696
|
function installHttpErrorHandler() {
|
|
638
|
-
const cfg = config.get();
|
|
639
|
-
if (!cfg.error?.enabled || !cfg.error?.httpError) {
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
697
|
interceptXHR();
|
|
643
698
|
interceptFetch();
|
|
644
|
-
if (cfg.debug) {
|
|
645
|
-
console.log('[Monitor] HTTP error handler installed');
|
|
646
|
-
}
|
|
647
699
|
}
|
|
648
|
-
/**
|
|
649
|
-
* 拦截 XMLHttpRequest
|
|
650
|
-
*/
|
|
651
700
|
/**
|
|
652
701
|
* 检查是否是 SDK 自身的请求
|
|
653
702
|
*/
|
|
654
703
|
function isSdkRequest(url) {
|
|
704
|
+
if (!config.isInitialized())
|
|
705
|
+
return false;
|
|
655
706
|
const cfg = config.get();
|
|
656
707
|
if (!cfg.dsn)
|
|
657
708
|
return false;
|
|
658
709
|
return url.includes(cfg.dsn);
|
|
659
710
|
}
|
|
711
|
+
/**
|
|
712
|
+
* 检查 HTTP 错误监控是否启用
|
|
713
|
+
*/
|
|
714
|
+
function isHttpErrorEnabled() {
|
|
715
|
+
if (!config.isInitialized())
|
|
716
|
+
return true; // 配置未就绪时默认启用,数据会被缓存
|
|
717
|
+
const cfg = config.get();
|
|
718
|
+
return cfg.error?.enabled !== false && cfg.error?.httpError !== false;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* 拦截 XMLHttpRequest
|
|
722
|
+
*/
|
|
660
723
|
function interceptXHR() {
|
|
661
724
|
XMLHttpRequest.prototype.open = function (method, url, async = true, username, password) {
|
|
662
725
|
const urlStr = url.toString();
|
|
@@ -686,11 +749,16 @@
|
|
|
686
749
|
this.addEventListener('loadend', function () {
|
|
687
750
|
if (!monitorData)
|
|
688
751
|
return;
|
|
752
|
+
if (!isHttpErrorEnabled())
|
|
753
|
+
return;
|
|
689
754
|
const duration = Date.now() - monitorData.startTime;
|
|
690
755
|
const status = this.status;
|
|
691
756
|
// 根据配置决定是否添加到面包屑
|
|
692
|
-
|
|
693
|
-
|
|
757
|
+
let recordMode = 'error';
|
|
758
|
+
if (config.isInitialized()) {
|
|
759
|
+
const cfg = config.get();
|
|
760
|
+
recordMode = cfg.behavior?.recordRequestBreadcrumb || 'error';
|
|
761
|
+
}
|
|
694
762
|
// 重定向(3xx)始终记录,不受配置控制
|
|
695
763
|
const isRedirect = status >= 300 && status < 400;
|
|
696
764
|
// 其他异常情况(0-网络错误、4xx-客户端错误、5xx-服务端错误)根据配置控制
|
|
@@ -737,11 +805,17 @@
|
|
|
737
805
|
const requestBody = typeof init?.body === 'string' ? init.body : undefined;
|
|
738
806
|
try {
|
|
739
807
|
const response = await originalFetch.call(window, input, init);
|
|
808
|
+
if (!isHttpErrorEnabled()) {
|
|
809
|
+
return response;
|
|
810
|
+
}
|
|
740
811
|
const duration = Date.now() - startTime;
|
|
741
812
|
const status = response.status;
|
|
742
813
|
// 根据配置决定是否添加到面包屑
|
|
743
|
-
|
|
744
|
-
|
|
814
|
+
let recordMode = 'error';
|
|
815
|
+
if (config.isInitialized()) {
|
|
816
|
+
const cfg = config.get();
|
|
817
|
+
recordMode = cfg.behavior?.recordRequestBreadcrumb || 'error';
|
|
818
|
+
}
|
|
745
819
|
// 重定向(3xx)始终记录,不受配置控制
|
|
746
820
|
const isRedirect = status >= 300 && status < 400;
|
|
747
821
|
// 其他异常情况根据配置控制
|
|
@@ -780,10 +854,16 @@
|
|
|
780
854
|
return response;
|
|
781
855
|
}
|
|
782
856
|
catch (error) {
|
|
857
|
+
if (!isHttpErrorEnabled()) {
|
|
858
|
+
throw error;
|
|
859
|
+
}
|
|
783
860
|
const duration = Date.now() - startTime;
|
|
784
861
|
// 网络错误时根据配置决定是否添加到面包屑
|
|
785
|
-
|
|
786
|
-
|
|
862
|
+
let recordMode = 'error';
|
|
863
|
+
if (config.isInitialized()) {
|
|
864
|
+
const cfg = config.get();
|
|
865
|
+
recordMode = cfg.behavior?.recordRequestBreadcrumb || 'error';
|
|
866
|
+
}
|
|
787
867
|
// 网络错误属于 error,当模式为 'all' 或 'error' 时都记录
|
|
788
868
|
if (recordMode !== 'none') {
|
|
789
869
|
context.addBreadcrumb({
|
|
@@ -839,10 +919,6 @@
|
|
|
839
919
|
* 安装白屏检测
|
|
840
920
|
*/
|
|
841
921
|
function installWhiteScreenDetector() {
|
|
842
|
-
const cfg = config.get();
|
|
843
|
-
if (!cfg.error?.enabled) {
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
922
|
// 在页面加载完成后检测
|
|
847
923
|
if (document.readyState === 'complete') {
|
|
848
924
|
scheduleDetection();
|
|
@@ -852,9 +928,6 @@
|
|
|
852
928
|
scheduleDetection();
|
|
853
929
|
});
|
|
854
930
|
}
|
|
855
|
-
if (cfg.debug) {
|
|
856
|
-
console.log('[Monitor] White screen detector installed');
|
|
857
|
-
}
|
|
858
931
|
}
|
|
859
932
|
/**
|
|
860
933
|
* 调度检测(延迟执行,给页面渲染时间)
|
|
@@ -862,6 +935,13 @@
|
|
|
862
935
|
function scheduleDetection() {
|
|
863
936
|
// 延迟 1 秒检测
|
|
864
937
|
setTimeout(() => {
|
|
938
|
+
// 检测时才检查配置
|
|
939
|
+
if (config.isInitialized()) {
|
|
940
|
+
const cfg = config.get();
|
|
941
|
+
if (!cfg.error?.enabled) {
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
865
945
|
const isWhiteScreen = detectWhiteScreen();
|
|
866
946
|
if (isWhiteScreen) {
|
|
867
947
|
reportWhiteScreen();
|
|
@@ -1441,13 +1521,8 @@
|
|
|
1441
1521
|
* ```
|
|
1442
1522
|
*/
|
|
1443
1523
|
const VueMonitorPlugin = {
|
|
1444
|
-
install(app, options) {
|
|
1445
|
-
//
|
|
1446
|
-
if (!options || (!options.configUrl && (!options.appId || !options.dsn))) {
|
|
1447
|
-
console.warn('[Monitor] VueMonitorPlugin requires either configUrl or (appId and dsn) in options');
|
|
1448
|
-
return;
|
|
1449
|
-
}
|
|
1450
|
-
// 初始化 SDK(如果尚未初始化)
|
|
1524
|
+
install(app, options = {}) {
|
|
1525
|
+
// 初始化 SDK(支持空配置,后续通过 updateConfig 激活)
|
|
1451
1526
|
try {
|
|
1452
1527
|
monitor.init(options);
|
|
1453
1528
|
}
|
|
@@ -1543,18 +1618,8 @@
|
|
|
1543
1618
|
* );
|
|
1544
1619
|
* ```
|
|
1545
1620
|
*/
|
|
1546
|
-
function createReactErrorBoundary(React, config) {
|
|
1547
|
-
//
|
|
1548
|
-
if (!config || (!config.configUrl && (!config.appId || !config.dsn))) {
|
|
1549
|
-
console.warn('[Monitor] createReactErrorBoundary requires either configUrl or (appId and dsn) in config');
|
|
1550
|
-
// 返回一个空的组件
|
|
1551
|
-
return class EmptyBoundary extends React.Component {
|
|
1552
|
-
render() {
|
|
1553
|
-
return this.props.children;
|
|
1554
|
-
}
|
|
1555
|
-
};
|
|
1556
|
-
}
|
|
1557
|
-
// 初始化 SDK(如果尚未初始化)
|
|
1621
|
+
function createReactErrorBoundary(React, config = {}) {
|
|
1622
|
+
// 初始化 SDK(支持空配置,后续通过 updateConfig 激活)
|
|
1558
1623
|
try {
|
|
1559
1624
|
monitor.init(config);
|
|
1560
1625
|
}
|
|
@@ -1650,64 +1715,131 @@
|
|
|
1650
1715
|
*/
|
|
1651
1716
|
class MonitorSDK {
|
|
1652
1717
|
constructor() {
|
|
1718
|
+
/** SDK 是否已初始化(基础初始化,可能还没有完整配置) */
|
|
1653
1719
|
this.initialized = false;
|
|
1654
|
-
|
|
1720
|
+
/** SDK 是否已就绪(配置完整,可以上报数据) */
|
|
1721
|
+
this.ready = false;
|
|
1722
|
+
/** 是否正在加载远程配置 */
|
|
1723
|
+
this.loading = false;
|
|
1724
|
+
/** 监控模块是否已安装 */
|
|
1725
|
+
this.monitorsInstalled = false;
|
|
1655
1726
|
}
|
|
1656
1727
|
/**
|
|
1657
1728
|
* 初始化 SDK
|
|
1658
|
-
*
|
|
1659
|
-
* 无需 await,SDK 内部会自动处理异步逻辑
|
|
1729
|
+
* 支持空配置初始化,后续通过 updateConfig 传入 configUrl 完成配置
|
|
1660
1730
|
*/
|
|
1661
|
-
init(userConfig) {
|
|
1662
|
-
if (this.initialized
|
|
1663
|
-
|
|
1664
|
-
console.warn('[Monitor] SDK already initialized');
|
|
1665
|
-
}
|
|
1731
|
+
init(userConfig = {}) {
|
|
1732
|
+
if (this.initialized) {
|
|
1733
|
+
console.warn('[Monitor] SDK already initialized');
|
|
1666
1734
|
return;
|
|
1667
1735
|
}
|
|
1668
|
-
// 如果配置了 configUrl
|
|
1736
|
+
// 如果配置了 configUrl,异步获取远程配置
|
|
1669
1737
|
if (userConfig.configUrl) {
|
|
1670
|
-
this.
|
|
1738
|
+
this.loading = true;
|
|
1739
|
+
this.initBasic(userConfig);
|
|
1671
1740
|
this.fetchRemoteConfig(userConfig.configUrl)
|
|
1672
1741
|
.then((remoteConfig) => {
|
|
1673
|
-
// 远程配置作为基础,用户本地配置覆盖远程配置
|
|
1674
1742
|
const finalConfig = this.mergeUserConfig(remoteConfig, userConfig);
|
|
1675
1743
|
if (userConfig.debug) {
|
|
1676
1744
|
console.log('[Monitor] Remote config loaded:', remoteConfig);
|
|
1677
1745
|
}
|
|
1678
|
-
this.
|
|
1746
|
+
this.activate(finalConfig);
|
|
1679
1747
|
})
|
|
1680
1748
|
.catch((error) => {
|
|
1681
|
-
this.
|
|
1749
|
+
this.loading = false;
|
|
1682
1750
|
console.error('[Monitor] Failed to fetch remote config:', error);
|
|
1683
1751
|
});
|
|
1684
1752
|
}
|
|
1753
|
+
else if (userConfig.appId && userConfig.dsn) {
|
|
1754
|
+
// 有完整配置,直接激活
|
|
1755
|
+
this.initBasic(userConfig);
|
|
1756
|
+
this.activate(userConfig);
|
|
1757
|
+
}
|
|
1685
1758
|
else {
|
|
1686
|
-
//
|
|
1687
|
-
this.
|
|
1759
|
+
// 空配置或不完整配置,只做基础初始化,等待后续 updateConfig
|
|
1760
|
+
this.initBasic(userConfig);
|
|
1761
|
+
console.log('[Monitor] SDK initialized in pending mode. Call updateConfig({configUrl}) to activate.');
|
|
1688
1762
|
}
|
|
1689
1763
|
}
|
|
1690
1764
|
/**
|
|
1691
|
-
*
|
|
1765
|
+
* 基础初始化(安装监控模块,但不开始上报)
|
|
1692
1766
|
*/
|
|
1693
|
-
|
|
1694
|
-
// 初始化配置
|
|
1695
|
-
config.init({
|
|
1696
|
-
...finalConfig,
|
|
1697
|
-
version: finalConfig.version || SDK_VERSION,
|
|
1698
|
-
}, isRemoteConfig);
|
|
1767
|
+
initBasic(userConfig) {
|
|
1699
1768
|
// 初始化上下文
|
|
1700
|
-
const maxBreadcrumbs =
|
|
1769
|
+
const maxBreadcrumbs = userConfig.behavior?.maxBreadcrumbs || 20;
|
|
1701
1770
|
context.init(maxBreadcrumbs);
|
|
1771
|
+
// 安装监控模块(早期捕获的数据会被缓存)
|
|
1772
|
+
if (!this.monitorsInstalled) {
|
|
1773
|
+
installErrorHandlers();
|
|
1774
|
+
installPerformanceMonitor();
|
|
1775
|
+
installBehaviorMonitor();
|
|
1776
|
+
this.monitorsInstalled = true;
|
|
1777
|
+
}
|
|
1702
1778
|
this.initialized = true;
|
|
1703
|
-
|
|
1779
|
+
if (userConfig.debug) {
|
|
1780
|
+
console.log('[Monitor] SDK basic initialized (monitors installed, waiting for config)');
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* 激活 SDK(配置完整后调用)
|
|
1785
|
+
*/
|
|
1786
|
+
activate(finalConfig) {
|
|
1787
|
+
// 初始化配置管理器
|
|
1788
|
+
config.init({
|
|
1789
|
+
...finalConfig,
|
|
1790
|
+
version: finalConfig.version || SDK_VERSION,
|
|
1791
|
+
}, true);
|
|
1792
|
+
this.ready = true;
|
|
1793
|
+
this.loading = false;
|
|
1794
|
+
// 通知 reporter 可以开始上报(会自动发送缓存的早期数据)
|
|
1795
|
+
reporter.setReady(true);
|
|
1704
1796
|
if (config.get().debug) {
|
|
1705
|
-
console.log('[Monitor] SDK
|
|
1797
|
+
console.log('[Monitor] SDK activated with config:', config.get());
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
/**
|
|
1801
|
+
* 动态更新配置
|
|
1802
|
+
* 如果传入 configUrl,会从远程获取配置并激活 SDK
|
|
1803
|
+
*/
|
|
1804
|
+
updateConfig(partialConfig) {
|
|
1805
|
+
// 如果传入了 configUrl,从远程获取配置
|
|
1806
|
+
if (partialConfig.configUrl) {
|
|
1807
|
+
console.log('[Monitor] Fetching remote config from:', partialConfig.configUrl);
|
|
1808
|
+
this.loading = true;
|
|
1809
|
+
this.fetchRemoteConfig(partialConfig.configUrl)
|
|
1810
|
+
.then((remoteConfig) => {
|
|
1811
|
+
const mergedConfig = this.mergeUserConfig(remoteConfig, partialConfig);
|
|
1812
|
+
// 移除 configUrl
|
|
1813
|
+
delete mergedConfig.configUrl;
|
|
1814
|
+
if (this.ready) {
|
|
1815
|
+
// 已就绪,更新配置
|
|
1816
|
+
config.update(mergedConfig);
|
|
1817
|
+
this.loading = false;
|
|
1818
|
+
if (config.get().debug) {
|
|
1819
|
+
console.log('[Monitor] Config updated from remote:', mergedConfig);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
else {
|
|
1823
|
+
// 未就绪,激活 SDK
|
|
1824
|
+
this.activate(mergedConfig);
|
|
1825
|
+
}
|
|
1826
|
+
})
|
|
1827
|
+
.catch((error) => {
|
|
1828
|
+
this.loading = false;
|
|
1829
|
+
console.error('[Monitor] Failed to fetch remote config:', error);
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
else {
|
|
1833
|
+
// 普通配置更新
|
|
1834
|
+
if (!this.ready) {
|
|
1835
|
+
console.warn('[Monitor] SDK not ready. Please provide configUrl or (appId + dsn) first.');
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
config.update(partialConfig);
|
|
1839
|
+
if (config.get().debug) {
|
|
1840
|
+
console.log('[Monitor] Config updated', partialConfig);
|
|
1841
|
+
}
|
|
1706
1842
|
}
|
|
1707
|
-
// 安装监控模块
|
|
1708
|
-
installErrorHandlers();
|
|
1709
|
-
installPerformanceMonitor();
|
|
1710
|
-
installBehaviorMonitor();
|
|
1711
1843
|
}
|
|
1712
1844
|
/**
|
|
1713
1845
|
* 从远程获取配置
|
|
@@ -1733,7 +1865,7 @@
|
|
|
1733
1865
|
if (Object.prototype.hasOwnProperty.call(local, key)) {
|
|
1734
1866
|
const localValue = local[key];
|
|
1735
1867
|
const remoteValue = remote[key];
|
|
1736
|
-
// 跳过 configUrl
|
|
1868
|
+
// 跳过 configUrl
|
|
1737
1869
|
if (key === 'configUrl')
|
|
1738
1870
|
continue;
|
|
1739
1871
|
if (localValue !== null &&
|
|
@@ -1754,55 +1886,33 @@
|
|
|
1754
1886
|
return result;
|
|
1755
1887
|
}
|
|
1756
1888
|
/**
|
|
1757
|
-
*
|
|
1889
|
+
* 检查 SDK 是否就绪
|
|
1758
1890
|
*/
|
|
1759
|
-
|
|
1760
|
-
this.
|
|
1761
|
-
config.setUser(user);
|
|
1891
|
+
isReady() {
|
|
1892
|
+
return this.ready;
|
|
1762
1893
|
}
|
|
1763
1894
|
/**
|
|
1764
|
-
*
|
|
1765
|
-
* @description 可以在运行时更新 SDK 配置,如 debug、sampling、report 等
|
|
1766
|
-
* 如果传入 configUrl,会从远程获取配置并合并
|
|
1895
|
+
* 设置用户信息
|
|
1767
1896
|
*/
|
|
1768
|
-
|
|
1769
|
-
this.
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
this.fetchRemoteConfig(partialConfig.configUrl)
|
|
1773
|
-
.then((remoteConfig) => {
|
|
1774
|
-
// 远程配置作为基础,传入的其他本地配置覆盖远程配置
|
|
1775
|
-
const mergedConfig = this.mergeUserConfig(remoteConfig, partialConfig);
|
|
1776
|
-
// 移除 configUrl,不需要存到配置里
|
|
1777
|
-
delete mergedConfig.configUrl;
|
|
1778
|
-
config.update(mergedConfig);
|
|
1779
|
-
if (config.get().debug) {
|
|
1780
|
-
console.log('[Monitor] Config updated from remote:', mergedConfig);
|
|
1781
|
-
}
|
|
1782
|
-
})
|
|
1783
|
-
.catch((error) => {
|
|
1784
|
-
console.error('[Monitor] Failed to fetch remote config:', error);
|
|
1785
|
-
});
|
|
1786
|
-
}
|
|
1787
|
-
else {
|
|
1788
|
-
config.update(partialConfig);
|
|
1789
|
-
if (config.get().debug) {
|
|
1790
|
-
console.log('[Monitor] Config updated', partialConfig);
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1897
|
+
setUser(user) {
|
|
1898
|
+
if (!this.ready)
|
|
1899
|
+
return;
|
|
1900
|
+
config.setUser(user);
|
|
1793
1901
|
}
|
|
1794
1902
|
/**
|
|
1795
1903
|
* 获取当前配置
|
|
1796
1904
|
*/
|
|
1797
1905
|
getConfig() {
|
|
1798
|
-
this.
|
|
1906
|
+
if (!this.ready)
|
|
1907
|
+
return null;
|
|
1799
1908
|
return config.get();
|
|
1800
1909
|
}
|
|
1801
1910
|
/**
|
|
1802
1911
|
* 手动上报错误
|
|
1803
1912
|
*/
|
|
1804
1913
|
captureError(error, extra) {
|
|
1805
|
-
this.
|
|
1914
|
+
if (!this.ready)
|
|
1915
|
+
return;
|
|
1806
1916
|
const errorData = {
|
|
1807
1917
|
type: 'js_error',
|
|
1808
1918
|
message: typeof error === 'string' ? error : error.message,
|
|
@@ -1815,7 +1925,8 @@
|
|
|
1815
1925
|
* 手动上报性能数据
|
|
1816
1926
|
*/
|
|
1817
1927
|
capturePerformance(metrics) {
|
|
1818
|
-
this.
|
|
1928
|
+
if (!this.ready)
|
|
1929
|
+
return;
|
|
1819
1930
|
reporter.reportPerformance({
|
|
1820
1931
|
type: 'performance',
|
|
1821
1932
|
metrics,
|
|
@@ -1825,7 +1936,8 @@
|
|
|
1825
1936
|
* 手动上报行为数据
|
|
1826
1937
|
*/
|
|
1827
1938
|
captureBehavior(action, data) {
|
|
1828
|
-
this.
|
|
1939
|
+
if (!this.ready)
|
|
1940
|
+
return;
|
|
1829
1941
|
reporter.reportBehavior({
|
|
1830
1942
|
type: 'behavior',
|
|
1831
1943
|
action,
|
|
@@ -1836,14 +1948,16 @@
|
|
|
1836
1948
|
* 添加面包屑
|
|
1837
1949
|
*/
|
|
1838
1950
|
addBreadcrumb(crumb) {
|
|
1839
|
-
this.
|
|
1951
|
+
if (!this.initialized)
|
|
1952
|
+
return;
|
|
1840
1953
|
context.addBreadcrumb(crumb);
|
|
1841
1954
|
}
|
|
1842
1955
|
/**
|
|
1843
1956
|
* 立即发送缓冲区数据
|
|
1844
1957
|
*/
|
|
1845
1958
|
flush() {
|
|
1846
|
-
this.
|
|
1959
|
+
if (!this.ready)
|
|
1960
|
+
return;
|
|
1847
1961
|
reporter.flush();
|
|
1848
1962
|
}
|
|
1849
1963
|
/**
|
|
@@ -1852,14 +1966,6 @@
|
|
|
1852
1966
|
getVersion() {
|
|
1853
1967
|
return SDK_VERSION;
|
|
1854
1968
|
}
|
|
1855
|
-
/**
|
|
1856
|
-
* 检查是否已初始化
|
|
1857
|
-
*/
|
|
1858
|
-
checkInit() {
|
|
1859
|
-
if (!this.initialized) {
|
|
1860
|
-
throw new Error('[Monitor] SDK not initialized. Please call init() first.');
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
1969
|
}
|
|
1864
1970
|
// 导出单例
|
|
1865
1971
|
const monitor = new MonitorSDK();
|