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.esm.js
CHANGED
|
@@ -45,27 +45,14 @@ class ConfigManager {
|
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* 初始化配置
|
|
48
|
-
* @param skipValidation 是否跳过必填校验(用于远程配置已获取 appId/dsn 的场景)
|
|
49
48
|
*/
|
|
50
49
|
init(userConfig, skipValidation = false) {
|
|
51
50
|
if (!skipValidation) {
|
|
52
|
-
// 如果没有 configUrl,则 appId 和 dsn 是必填的
|
|
53
|
-
if (!userConfig.configUrl) {
|
|
54
|
-
if (!userConfig.appId) {
|
|
55
|
-
throw new Error('[Monitor] appId is required when configUrl is not provided');
|
|
56
|
-
}
|
|
57
|
-
if (!userConfig.dsn) {
|
|
58
|
-
throw new Error('[Monitor] dsn is required when configUrl is not provided');
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
// 远程配置模式下,校验合并后的配置
|
|
64
51
|
if (!userConfig.appId) {
|
|
65
|
-
throw new Error('[Monitor] appId is required
|
|
52
|
+
throw new Error('[Monitor] appId is required');
|
|
66
53
|
}
|
|
67
54
|
if (!userConfig.dsn) {
|
|
68
|
-
throw new Error('[Monitor] dsn is required
|
|
55
|
+
throw new Error('[Monitor] dsn is required');
|
|
69
56
|
}
|
|
70
57
|
}
|
|
71
58
|
this.config = this.mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
@@ -289,6 +276,13 @@ class Sender {
|
|
|
289
276
|
doSend(data) {
|
|
290
277
|
const cfg = config.get();
|
|
291
278
|
const dsn = cfg.dsn;
|
|
279
|
+
// dsn 未配置,跳过发送
|
|
280
|
+
if (!dsn) {
|
|
281
|
+
if (cfg.debug) {
|
|
282
|
+
console.warn('[Monitor] dsn not configured, skip sending');
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
292
286
|
const payload = JSON.stringify(data);
|
|
293
287
|
// 优先使用 sendBeacon(更可靠、异步、不阻塞页面)
|
|
294
288
|
if (supportsSendBeacon()) {
|
|
@@ -387,13 +381,63 @@ const SDK_VERSION$1 = '1.0.0';
|
|
|
387
381
|
* 上报器类
|
|
388
382
|
*/
|
|
389
383
|
class Reporter {
|
|
384
|
+
constructor() {
|
|
385
|
+
/** SDK 是否就绪 */
|
|
386
|
+
this.ready = false;
|
|
387
|
+
/** 早期数据缓存队列(SDK 未就绪时缓存) */
|
|
388
|
+
this.earlyBuffer = [];
|
|
389
|
+
/** 最大缓存数量 */
|
|
390
|
+
this.maxEarlyBufferSize = 50;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* 设置 SDK 就绪状态
|
|
394
|
+
*/
|
|
395
|
+
setReady(ready) {
|
|
396
|
+
this.ready = ready;
|
|
397
|
+
// 就绪后发送缓存的早期数据
|
|
398
|
+
if (ready && this.earlyBuffer.length > 0) {
|
|
399
|
+
console.log(`[Monitor] Flushing ${this.earlyBuffer.length} early buffered items`);
|
|
400
|
+
this.flushEarlyBuffer();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* 发送早期缓存的数据
|
|
405
|
+
*/
|
|
406
|
+
flushEarlyBuffer() {
|
|
407
|
+
while (this.earlyBuffer.length > 0) {
|
|
408
|
+
const item = this.earlyBuffer.shift();
|
|
409
|
+
if (!item)
|
|
410
|
+
continue;
|
|
411
|
+
switch (item.type) {
|
|
412
|
+
case 'error':
|
|
413
|
+
this.reportError(item.data);
|
|
414
|
+
break;
|
|
415
|
+
case 'performance':
|
|
416
|
+
this.reportPerformance(item.data);
|
|
417
|
+
break;
|
|
418
|
+
case 'behavior':
|
|
419
|
+
this.reportBehavior(item.data);
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* 缓存早期数据
|
|
426
|
+
*/
|
|
427
|
+
bufferEarly(type, data) {
|
|
428
|
+
if (this.earlyBuffer.length >= this.maxEarlyBufferSize) {
|
|
429
|
+
// 缓存已满,丢弃最早的数据
|
|
430
|
+
this.earlyBuffer.shift();
|
|
431
|
+
}
|
|
432
|
+
this.earlyBuffer.push({ type, data });
|
|
433
|
+
}
|
|
390
434
|
/**
|
|
391
435
|
* 构建基础数据
|
|
392
436
|
*/
|
|
393
437
|
buildBaseData() {
|
|
394
438
|
const cfg = config.get();
|
|
395
439
|
return {
|
|
396
|
-
appId: cfg.appId,
|
|
440
|
+
appId: cfg.appId || '',
|
|
397
441
|
userId: cfg.user?.userId,
|
|
398
442
|
sessionId: context.getSessionId(),
|
|
399
443
|
pageUrl: getPageUrl(),
|
|
@@ -414,6 +458,11 @@ class Reporter {
|
|
|
414
458
|
* 上报错误
|
|
415
459
|
*/
|
|
416
460
|
reportError(data) {
|
|
461
|
+
// SDK 未就绪时缓存数据
|
|
462
|
+
if (!this.ready) {
|
|
463
|
+
this.bufferEarly('error', data);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
417
466
|
if (!this.shouldSample('error'))
|
|
418
467
|
return;
|
|
419
468
|
const cfg = config.get();
|
|
@@ -435,6 +484,11 @@ class Reporter {
|
|
|
435
484
|
* 上报性能
|
|
436
485
|
*/
|
|
437
486
|
reportPerformance(data) {
|
|
487
|
+
// SDK 未就绪时缓存数据
|
|
488
|
+
if (!this.ready) {
|
|
489
|
+
this.bufferEarly('performance', data);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
438
492
|
if (!this.shouldSample('performance'))
|
|
439
493
|
return;
|
|
440
494
|
const reportData = {
|
|
@@ -447,6 +501,11 @@ class Reporter {
|
|
|
447
501
|
* 上报行为
|
|
448
502
|
*/
|
|
449
503
|
reportBehavior(data) {
|
|
504
|
+
// SDK 未就绪时缓存数据
|
|
505
|
+
if (!this.ready) {
|
|
506
|
+
this.bufferEarly('behavior', data);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
450
509
|
if (!this.shouldSample('behavior'))
|
|
451
510
|
return;
|
|
452
511
|
const reportData = {
|
|
@@ -488,11 +547,14 @@ const reporter = new Reporter();
|
|
|
488
547
|
* 安装 JS 错误监听
|
|
489
548
|
*/
|
|
490
549
|
function installJsErrorHandler() {
|
|
491
|
-
const cfg = config.get();
|
|
492
|
-
if (!cfg.error?.enabled || !cfg.error?.jsError) {
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
550
|
window.addEventListener('error', (event) => {
|
|
551
|
+
// 延迟检查配置(SDK 可能还在加载配置)
|
|
552
|
+
if (config.isInitialized()) {
|
|
553
|
+
const cfg = config.get();
|
|
554
|
+
if (!cfg.error?.enabled || !cfg.error?.jsError) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
496
558
|
// 过滤资源加载错误(由 resourceError 处理)
|
|
497
559
|
if (event.target !== window) {
|
|
498
560
|
return;
|
|
@@ -517,9 +579,6 @@ function installJsErrorHandler() {
|
|
|
517
579
|
});
|
|
518
580
|
reporter.reportError(errorData);
|
|
519
581
|
}, true);
|
|
520
|
-
if (cfg.debug) {
|
|
521
|
-
console.log('[Monitor] JS error handler installed');
|
|
522
|
-
}
|
|
523
582
|
}
|
|
524
583
|
|
|
525
584
|
/**
|
|
@@ -529,11 +588,14 @@ function installJsErrorHandler() {
|
|
|
529
588
|
* 安装 Promise 错误监听
|
|
530
589
|
*/
|
|
531
590
|
function installPromiseErrorHandler() {
|
|
532
|
-
const cfg = config.get();
|
|
533
|
-
if (!cfg.error?.enabled || !cfg.error?.promiseError) {
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
591
|
window.addEventListener('unhandledrejection', (event) => {
|
|
592
|
+
// 延迟检查配置(SDK 可能还在加载配置)
|
|
593
|
+
if (config.isInitialized()) {
|
|
594
|
+
const cfg = config.get();
|
|
595
|
+
if (!cfg.error?.enabled || !cfg.error?.promiseError) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
537
599
|
let message = 'Unhandled Promise rejection';
|
|
538
600
|
let stack;
|
|
539
601
|
const reason = event.reason;
|
|
@@ -562,9 +624,6 @@ function installPromiseErrorHandler() {
|
|
|
562
624
|
});
|
|
563
625
|
reporter.reportError(errorData);
|
|
564
626
|
});
|
|
565
|
-
if (cfg.debug) {
|
|
566
|
-
console.log('[Monitor] Promise error handler installed');
|
|
567
|
-
}
|
|
568
627
|
}
|
|
569
628
|
|
|
570
629
|
/**
|
|
@@ -574,11 +633,14 @@ function installPromiseErrorHandler() {
|
|
|
574
633
|
* 安装资源错误监听
|
|
575
634
|
*/
|
|
576
635
|
function installResourceErrorHandler() {
|
|
577
|
-
const cfg = config.get();
|
|
578
|
-
if (!cfg.error?.enabled || !cfg.error?.resourceError) {
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
636
|
window.addEventListener('error', (event) => {
|
|
637
|
+
// 延迟检查配置(SDK 可能还在加载配置)
|
|
638
|
+
if (config.isInitialized()) {
|
|
639
|
+
const cfg = config.get();
|
|
640
|
+
if (!cfg.error?.enabled || !cfg.error?.resourceError) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
582
644
|
const target = event.target;
|
|
583
645
|
// 只处理资源加载错误(target 不是 window)
|
|
584
646
|
if (!target || target === window || !(target instanceof HTMLElement)) {
|
|
@@ -612,9 +674,6 @@ function installResourceErrorHandler() {
|
|
|
612
674
|
});
|
|
613
675
|
reporter.reportError(errorData);
|
|
614
676
|
}, true); // 使用捕获阶段
|
|
615
|
-
if (cfg.debug) {
|
|
616
|
-
console.log('[Monitor] Resource error handler installed');
|
|
617
|
-
}
|
|
618
677
|
}
|
|
619
678
|
|
|
620
679
|
/**
|
|
@@ -629,28 +688,32 @@ const originalFetch = window.fetch;
|
|
|
629
688
|
* 安装 HTTP 错误拦截
|
|
630
689
|
*/
|
|
631
690
|
function installHttpErrorHandler() {
|
|
632
|
-
const cfg = config.get();
|
|
633
|
-
if (!cfg.error?.enabled || !cfg.error?.httpError) {
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
691
|
interceptXHR();
|
|
637
692
|
interceptFetch();
|
|
638
|
-
if (cfg.debug) {
|
|
639
|
-
console.log('[Monitor] HTTP error handler installed');
|
|
640
|
-
}
|
|
641
693
|
}
|
|
642
|
-
/**
|
|
643
|
-
* 拦截 XMLHttpRequest
|
|
644
|
-
*/
|
|
645
694
|
/**
|
|
646
695
|
* 检查是否是 SDK 自身的请求
|
|
647
696
|
*/
|
|
648
697
|
function isSdkRequest(url) {
|
|
698
|
+
if (!config.isInitialized())
|
|
699
|
+
return false;
|
|
649
700
|
const cfg = config.get();
|
|
650
701
|
if (!cfg.dsn)
|
|
651
702
|
return false;
|
|
652
703
|
return url.includes(cfg.dsn);
|
|
653
704
|
}
|
|
705
|
+
/**
|
|
706
|
+
* 检查 HTTP 错误监控是否启用
|
|
707
|
+
*/
|
|
708
|
+
function isHttpErrorEnabled() {
|
|
709
|
+
if (!config.isInitialized())
|
|
710
|
+
return true; // 配置未就绪时默认启用,数据会被缓存
|
|
711
|
+
const cfg = config.get();
|
|
712
|
+
return cfg.error?.enabled !== false && cfg.error?.httpError !== false;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* 拦截 XMLHttpRequest
|
|
716
|
+
*/
|
|
654
717
|
function interceptXHR() {
|
|
655
718
|
XMLHttpRequest.prototype.open = function (method, url, async = true, username, password) {
|
|
656
719
|
const urlStr = url.toString();
|
|
@@ -680,11 +743,16 @@ function interceptXHR() {
|
|
|
680
743
|
this.addEventListener('loadend', function () {
|
|
681
744
|
if (!monitorData)
|
|
682
745
|
return;
|
|
746
|
+
if (!isHttpErrorEnabled())
|
|
747
|
+
return;
|
|
683
748
|
const duration = Date.now() - monitorData.startTime;
|
|
684
749
|
const status = this.status;
|
|
685
750
|
// 根据配置决定是否添加到面包屑
|
|
686
|
-
|
|
687
|
-
|
|
751
|
+
let recordMode = 'error';
|
|
752
|
+
if (config.isInitialized()) {
|
|
753
|
+
const cfg = config.get();
|
|
754
|
+
recordMode = cfg.behavior?.recordRequestBreadcrumb || 'error';
|
|
755
|
+
}
|
|
688
756
|
// 重定向(3xx)始终记录,不受配置控制
|
|
689
757
|
const isRedirect = status >= 300 && status < 400;
|
|
690
758
|
// 其他异常情况(0-网络错误、4xx-客户端错误、5xx-服务端错误)根据配置控制
|
|
@@ -731,11 +799,17 @@ function interceptFetch() {
|
|
|
731
799
|
const requestBody = typeof init?.body === 'string' ? init.body : undefined;
|
|
732
800
|
try {
|
|
733
801
|
const response = await originalFetch.call(window, input, init);
|
|
802
|
+
if (!isHttpErrorEnabled()) {
|
|
803
|
+
return response;
|
|
804
|
+
}
|
|
734
805
|
const duration = Date.now() - startTime;
|
|
735
806
|
const status = response.status;
|
|
736
807
|
// 根据配置决定是否添加到面包屑
|
|
737
|
-
|
|
738
|
-
|
|
808
|
+
let recordMode = 'error';
|
|
809
|
+
if (config.isInitialized()) {
|
|
810
|
+
const cfg = config.get();
|
|
811
|
+
recordMode = cfg.behavior?.recordRequestBreadcrumb || 'error';
|
|
812
|
+
}
|
|
739
813
|
// 重定向(3xx)始终记录,不受配置控制
|
|
740
814
|
const isRedirect = status >= 300 && status < 400;
|
|
741
815
|
// 其他异常情况根据配置控制
|
|
@@ -774,10 +848,16 @@ function interceptFetch() {
|
|
|
774
848
|
return response;
|
|
775
849
|
}
|
|
776
850
|
catch (error) {
|
|
851
|
+
if (!isHttpErrorEnabled()) {
|
|
852
|
+
throw error;
|
|
853
|
+
}
|
|
777
854
|
const duration = Date.now() - startTime;
|
|
778
855
|
// 网络错误时根据配置决定是否添加到面包屑
|
|
779
|
-
|
|
780
|
-
|
|
856
|
+
let recordMode = 'error';
|
|
857
|
+
if (config.isInitialized()) {
|
|
858
|
+
const cfg = config.get();
|
|
859
|
+
recordMode = cfg.behavior?.recordRequestBreadcrumb || 'error';
|
|
860
|
+
}
|
|
781
861
|
// 网络错误属于 error,当模式为 'all' 或 'error' 时都记录
|
|
782
862
|
if (recordMode !== 'none') {
|
|
783
863
|
context.addBreadcrumb({
|
|
@@ -833,10 +913,6 @@ const INVALID_ELEMENTS = ['html', 'body', 'head', 'meta', 'link', 'style', 'scri
|
|
|
833
913
|
* 安装白屏检测
|
|
834
914
|
*/
|
|
835
915
|
function installWhiteScreenDetector() {
|
|
836
|
-
const cfg = config.get();
|
|
837
|
-
if (!cfg.error?.enabled) {
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
916
|
// 在页面加载完成后检测
|
|
841
917
|
if (document.readyState === 'complete') {
|
|
842
918
|
scheduleDetection();
|
|
@@ -846,9 +922,6 @@ function installWhiteScreenDetector() {
|
|
|
846
922
|
scheduleDetection();
|
|
847
923
|
});
|
|
848
924
|
}
|
|
849
|
-
if (cfg.debug) {
|
|
850
|
-
console.log('[Monitor] White screen detector installed');
|
|
851
|
-
}
|
|
852
925
|
}
|
|
853
926
|
/**
|
|
854
927
|
* 调度检测(延迟执行,给页面渲染时间)
|
|
@@ -856,6 +929,13 @@ function installWhiteScreenDetector() {
|
|
|
856
929
|
function scheduleDetection() {
|
|
857
930
|
// 延迟 1 秒检测
|
|
858
931
|
setTimeout(() => {
|
|
932
|
+
// 检测时才检查配置
|
|
933
|
+
if (config.isInitialized()) {
|
|
934
|
+
const cfg = config.get();
|
|
935
|
+
if (!cfg.error?.enabled) {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
859
939
|
const isWhiteScreen = detectWhiteScreen();
|
|
860
940
|
if (isWhiteScreen) {
|
|
861
941
|
reportWhiteScreen();
|
|
@@ -1435,13 +1515,8 @@ function installBehaviorMonitor() {
|
|
|
1435
1515
|
* ```
|
|
1436
1516
|
*/
|
|
1437
1517
|
const VueMonitorPlugin = {
|
|
1438
|
-
install(app, options) {
|
|
1439
|
-
//
|
|
1440
|
-
if (!options || (!options.configUrl && (!options.appId || !options.dsn))) {
|
|
1441
|
-
console.warn('[Monitor] VueMonitorPlugin requires either configUrl or (appId and dsn) in options');
|
|
1442
|
-
return;
|
|
1443
|
-
}
|
|
1444
|
-
// 初始化 SDK(如果尚未初始化)
|
|
1518
|
+
install(app, options = {}) {
|
|
1519
|
+
// 初始化 SDK(支持空配置,后续通过 updateConfig 激活)
|
|
1445
1520
|
try {
|
|
1446
1521
|
monitor.init(options);
|
|
1447
1522
|
}
|
|
@@ -1537,18 +1612,8 @@ const VueMonitorPlugin = {
|
|
|
1537
1612
|
* );
|
|
1538
1613
|
* ```
|
|
1539
1614
|
*/
|
|
1540
|
-
function createReactErrorBoundary(React, config) {
|
|
1541
|
-
//
|
|
1542
|
-
if (!config || (!config.configUrl && (!config.appId || !config.dsn))) {
|
|
1543
|
-
console.warn('[Monitor] createReactErrorBoundary requires either configUrl or (appId and dsn) in config');
|
|
1544
|
-
// 返回一个空的组件
|
|
1545
|
-
return class EmptyBoundary extends React.Component {
|
|
1546
|
-
render() {
|
|
1547
|
-
return this.props.children;
|
|
1548
|
-
}
|
|
1549
|
-
};
|
|
1550
|
-
}
|
|
1551
|
-
// 初始化 SDK(如果尚未初始化)
|
|
1615
|
+
function createReactErrorBoundary(React, config = {}) {
|
|
1616
|
+
// 初始化 SDK(支持空配置,后续通过 updateConfig 激活)
|
|
1552
1617
|
try {
|
|
1553
1618
|
monitor.init(config);
|
|
1554
1619
|
}
|
|
@@ -1644,64 +1709,131 @@ const SDK_VERSION = '1.0.0';
|
|
|
1644
1709
|
*/
|
|
1645
1710
|
class MonitorSDK {
|
|
1646
1711
|
constructor() {
|
|
1712
|
+
/** SDK 是否已初始化(基础初始化,可能还没有完整配置) */
|
|
1647
1713
|
this.initialized = false;
|
|
1648
|
-
|
|
1714
|
+
/** SDK 是否已就绪(配置完整,可以上报数据) */
|
|
1715
|
+
this.ready = false;
|
|
1716
|
+
/** 是否正在加载远程配置 */
|
|
1717
|
+
this.loading = false;
|
|
1718
|
+
/** 监控模块是否已安装 */
|
|
1719
|
+
this.monitorsInstalled = false;
|
|
1649
1720
|
}
|
|
1650
1721
|
/**
|
|
1651
1722
|
* 初始化 SDK
|
|
1652
|
-
*
|
|
1653
|
-
* 无需 await,SDK 内部会自动处理异步逻辑
|
|
1723
|
+
* 支持空配置初始化,后续通过 updateConfig 传入 configUrl 完成配置
|
|
1654
1724
|
*/
|
|
1655
|
-
init(userConfig) {
|
|
1656
|
-
if (this.initialized
|
|
1657
|
-
|
|
1658
|
-
console.warn('[Monitor] SDK already initialized');
|
|
1659
|
-
}
|
|
1725
|
+
init(userConfig = {}) {
|
|
1726
|
+
if (this.initialized) {
|
|
1727
|
+
console.warn('[Monitor] SDK already initialized');
|
|
1660
1728
|
return;
|
|
1661
1729
|
}
|
|
1662
|
-
// 如果配置了 configUrl
|
|
1730
|
+
// 如果配置了 configUrl,异步获取远程配置
|
|
1663
1731
|
if (userConfig.configUrl) {
|
|
1664
|
-
this.
|
|
1732
|
+
this.loading = true;
|
|
1733
|
+
this.initBasic(userConfig);
|
|
1665
1734
|
this.fetchRemoteConfig(userConfig.configUrl)
|
|
1666
1735
|
.then((remoteConfig) => {
|
|
1667
|
-
// 远程配置作为基础,用户本地配置覆盖远程配置
|
|
1668
1736
|
const finalConfig = this.mergeUserConfig(remoteConfig, userConfig);
|
|
1669
1737
|
if (userConfig.debug) {
|
|
1670
1738
|
console.log('[Monitor] Remote config loaded:', remoteConfig);
|
|
1671
1739
|
}
|
|
1672
|
-
this.
|
|
1740
|
+
this.activate(finalConfig);
|
|
1673
1741
|
})
|
|
1674
1742
|
.catch((error) => {
|
|
1675
|
-
this.
|
|
1743
|
+
this.loading = false;
|
|
1676
1744
|
console.error('[Monitor] Failed to fetch remote config:', error);
|
|
1677
1745
|
});
|
|
1678
1746
|
}
|
|
1747
|
+
else if (userConfig.appId && userConfig.dsn) {
|
|
1748
|
+
// 有完整配置,直接激活
|
|
1749
|
+
this.initBasic(userConfig);
|
|
1750
|
+
this.activate(userConfig);
|
|
1751
|
+
}
|
|
1679
1752
|
else {
|
|
1680
|
-
//
|
|
1681
|
-
this.
|
|
1753
|
+
// 空配置或不完整配置,只做基础初始化,等待后续 updateConfig
|
|
1754
|
+
this.initBasic(userConfig);
|
|
1755
|
+
console.log('[Monitor] SDK initialized in pending mode. Call updateConfig({configUrl}) to activate.');
|
|
1682
1756
|
}
|
|
1683
1757
|
}
|
|
1684
1758
|
/**
|
|
1685
|
-
*
|
|
1759
|
+
* 基础初始化(安装监控模块,但不开始上报)
|
|
1686
1760
|
*/
|
|
1687
|
-
|
|
1688
|
-
// 初始化配置
|
|
1689
|
-
config.init({
|
|
1690
|
-
...finalConfig,
|
|
1691
|
-
version: finalConfig.version || SDK_VERSION,
|
|
1692
|
-
}, isRemoteConfig);
|
|
1761
|
+
initBasic(userConfig) {
|
|
1693
1762
|
// 初始化上下文
|
|
1694
|
-
const maxBreadcrumbs =
|
|
1763
|
+
const maxBreadcrumbs = userConfig.behavior?.maxBreadcrumbs || 20;
|
|
1695
1764
|
context.init(maxBreadcrumbs);
|
|
1765
|
+
// 安装监控模块(早期捕获的数据会被缓存)
|
|
1766
|
+
if (!this.monitorsInstalled) {
|
|
1767
|
+
installErrorHandlers();
|
|
1768
|
+
installPerformanceMonitor();
|
|
1769
|
+
installBehaviorMonitor();
|
|
1770
|
+
this.monitorsInstalled = true;
|
|
1771
|
+
}
|
|
1696
1772
|
this.initialized = true;
|
|
1697
|
-
|
|
1773
|
+
if (userConfig.debug) {
|
|
1774
|
+
console.log('[Monitor] SDK basic initialized (monitors installed, waiting for config)');
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* 激活 SDK(配置完整后调用)
|
|
1779
|
+
*/
|
|
1780
|
+
activate(finalConfig) {
|
|
1781
|
+
// 初始化配置管理器
|
|
1782
|
+
config.init({
|
|
1783
|
+
...finalConfig,
|
|
1784
|
+
version: finalConfig.version || SDK_VERSION,
|
|
1785
|
+
}, true);
|
|
1786
|
+
this.ready = true;
|
|
1787
|
+
this.loading = false;
|
|
1788
|
+
// 通知 reporter 可以开始上报(会自动发送缓存的早期数据)
|
|
1789
|
+
reporter.setReady(true);
|
|
1698
1790
|
if (config.get().debug) {
|
|
1699
|
-
console.log('[Monitor] SDK
|
|
1791
|
+
console.log('[Monitor] SDK activated with config:', config.get());
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* 动态更新配置
|
|
1796
|
+
* 如果传入 configUrl,会从远程获取配置并激活 SDK
|
|
1797
|
+
*/
|
|
1798
|
+
updateConfig(partialConfig) {
|
|
1799
|
+
// 如果传入了 configUrl,从远程获取配置
|
|
1800
|
+
if (partialConfig.configUrl) {
|
|
1801
|
+
console.log('[Monitor] Fetching remote config from:', partialConfig.configUrl);
|
|
1802
|
+
this.loading = true;
|
|
1803
|
+
this.fetchRemoteConfig(partialConfig.configUrl)
|
|
1804
|
+
.then((remoteConfig) => {
|
|
1805
|
+
const mergedConfig = this.mergeUserConfig(remoteConfig, partialConfig);
|
|
1806
|
+
// 移除 configUrl
|
|
1807
|
+
delete mergedConfig.configUrl;
|
|
1808
|
+
if (this.ready) {
|
|
1809
|
+
// 已就绪,更新配置
|
|
1810
|
+
config.update(mergedConfig);
|
|
1811
|
+
this.loading = false;
|
|
1812
|
+
if (config.get().debug) {
|
|
1813
|
+
console.log('[Monitor] Config updated from remote:', mergedConfig);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
else {
|
|
1817
|
+
// 未就绪,激活 SDK
|
|
1818
|
+
this.activate(mergedConfig);
|
|
1819
|
+
}
|
|
1820
|
+
})
|
|
1821
|
+
.catch((error) => {
|
|
1822
|
+
this.loading = false;
|
|
1823
|
+
console.error('[Monitor] Failed to fetch remote config:', error);
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
else {
|
|
1827
|
+
// 普通配置更新
|
|
1828
|
+
if (!this.ready) {
|
|
1829
|
+
console.warn('[Monitor] SDK not ready. Please provide configUrl or (appId + dsn) first.');
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
config.update(partialConfig);
|
|
1833
|
+
if (config.get().debug) {
|
|
1834
|
+
console.log('[Monitor] Config updated', partialConfig);
|
|
1835
|
+
}
|
|
1700
1836
|
}
|
|
1701
|
-
// 安装监控模块
|
|
1702
|
-
installErrorHandlers();
|
|
1703
|
-
installPerformanceMonitor();
|
|
1704
|
-
installBehaviorMonitor();
|
|
1705
1837
|
}
|
|
1706
1838
|
/**
|
|
1707
1839
|
* 从远程获取配置
|
|
@@ -1727,7 +1859,7 @@ class MonitorSDK {
|
|
|
1727
1859
|
if (Object.prototype.hasOwnProperty.call(local, key)) {
|
|
1728
1860
|
const localValue = local[key];
|
|
1729
1861
|
const remoteValue = remote[key];
|
|
1730
|
-
// 跳过 configUrl
|
|
1862
|
+
// 跳过 configUrl
|
|
1731
1863
|
if (key === 'configUrl')
|
|
1732
1864
|
continue;
|
|
1733
1865
|
if (localValue !== null &&
|
|
@@ -1748,55 +1880,33 @@ class MonitorSDK {
|
|
|
1748
1880
|
return result;
|
|
1749
1881
|
}
|
|
1750
1882
|
/**
|
|
1751
|
-
*
|
|
1883
|
+
* 检查 SDK 是否就绪
|
|
1752
1884
|
*/
|
|
1753
|
-
|
|
1754
|
-
this.
|
|
1755
|
-
config.setUser(user);
|
|
1885
|
+
isReady() {
|
|
1886
|
+
return this.ready;
|
|
1756
1887
|
}
|
|
1757
1888
|
/**
|
|
1758
|
-
*
|
|
1759
|
-
* @description 可以在运行时更新 SDK 配置,如 debug、sampling、report 等
|
|
1760
|
-
* 如果传入 configUrl,会从远程获取配置并合并
|
|
1889
|
+
* 设置用户信息
|
|
1761
1890
|
*/
|
|
1762
|
-
|
|
1763
|
-
this.
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
this.fetchRemoteConfig(partialConfig.configUrl)
|
|
1767
|
-
.then((remoteConfig) => {
|
|
1768
|
-
// 远程配置作为基础,传入的其他本地配置覆盖远程配置
|
|
1769
|
-
const mergedConfig = this.mergeUserConfig(remoteConfig, partialConfig);
|
|
1770
|
-
// 移除 configUrl,不需要存到配置里
|
|
1771
|
-
delete mergedConfig.configUrl;
|
|
1772
|
-
config.update(mergedConfig);
|
|
1773
|
-
if (config.get().debug) {
|
|
1774
|
-
console.log('[Monitor] Config updated from remote:', mergedConfig);
|
|
1775
|
-
}
|
|
1776
|
-
})
|
|
1777
|
-
.catch((error) => {
|
|
1778
|
-
console.error('[Monitor] Failed to fetch remote config:', error);
|
|
1779
|
-
});
|
|
1780
|
-
}
|
|
1781
|
-
else {
|
|
1782
|
-
config.update(partialConfig);
|
|
1783
|
-
if (config.get().debug) {
|
|
1784
|
-
console.log('[Monitor] Config updated', partialConfig);
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1891
|
+
setUser(user) {
|
|
1892
|
+
if (!this.ready)
|
|
1893
|
+
return;
|
|
1894
|
+
config.setUser(user);
|
|
1787
1895
|
}
|
|
1788
1896
|
/**
|
|
1789
1897
|
* 获取当前配置
|
|
1790
1898
|
*/
|
|
1791
1899
|
getConfig() {
|
|
1792
|
-
this.
|
|
1900
|
+
if (!this.ready)
|
|
1901
|
+
return null;
|
|
1793
1902
|
return config.get();
|
|
1794
1903
|
}
|
|
1795
1904
|
/**
|
|
1796
1905
|
* 手动上报错误
|
|
1797
1906
|
*/
|
|
1798
1907
|
captureError(error, extra) {
|
|
1799
|
-
this.
|
|
1908
|
+
if (!this.ready)
|
|
1909
|
+
return;
|
|
1800
1910
|
const errorData = {
|
|
1801
1911
|
type: 'js_error',
|
|
1802
1912
|
message: typeof error === 'string' ? error : error.message,
|
|
@@ -1809,7 +1919,8 @@ class MonitorSDK {
|
|
|
1809
1919
|
* 手动上报性能数据
|
|
1810
1920
|
*/
|
|
1811
1921
|
capturePerformance(metrics) {
|
|
1812
|
-
this.
|
|
1922
|
+
if (!this.ready)
|
|
1923
|
+
return;
|
|
1813
1924
|
reporter.reportPerformance({
|
|
1814
1925
|
type: 'performance',
|
|
1815
1926
|
metrics,
|
|
@@ -1819,7 +1930,8 @@ class MonitorSDK {
|
|
|
1819
1930
|
* 手动上报行为数据
|
|
1820
1931
|
*/
|
|
1821
1932
|
captureBehavior(action, data) {
|
|
1822
|
-
this.
|
|
1933
|
+
if (!this.ready)
|
|
1934
|
+
return;
|
|
1823
1935
|
reporter.reportBehavior({
|
|
1824
1936
|
type: 'behavior',
|
|
1825
1937
|
action,
|
|
@@ -1830,14 +1942,16 @@ class MonitorSDK {
|
|
|
1830
1942
|
* 添加面包屑
|
|
1831
1943
|
*/
|
|
1832
1944
|
addBreadcrumb(crumb) {
|
|
1833
|
-
this.
|
|
1945
|
+
if (!this.initialized)
|
|
1946
|
+
return;
|
|
1834
1947
|
context.addBreadcrumb(crumb);
|
|
1835
1948
|
}
|
|
1836
1949
|
/**
|
|
1837
1950
|
* 立即发送缓冲区数据
|
|
1838
1951
|
*/
|
|
1839
1952
|
flush() {
|
|
1840
|
-
this.
|
|
1953
|
+
if (!this.ready)
|
|
1954
|
+
return;
|
|
1841
1955
|
reporter.flush();
|
|
1842
1956
|
}
|
|
1843
1957
|
/**
|
|
@@ -1846,14 +1960,6 @@ class MonitorSDK {
|
|
|
1846
1960
|
getVersion() {
|
|
1847
1961
|
return SDK_VERSION;
|
|
1848
1962
|
}
|
|
1849
|
-
/**
|
|
1850
|
-
* 检查是否已初始化
|
|
1851
|
-
*/
|
|
1852
|
-
checkInit() {
|
|
1853
|
-
if (!this.initialized) {
|
|
1854
|
-
throw new Error('[Monitor] SDK not initialized. Please call init() first.');
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
1963
|
}
|
|
1858
1964
|
// 导出单例
|
|
1859
1965
|
const monitor = new MonitorSDK();
|