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