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/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 (not found in remote config)');
52
+ throw new Error('[Monitor] appId is required');
66
53
  }
67
54
  if (!userConfig.dsn) {
68
- throw new Error('[Monitor] dsn is required (not found in remote config)');
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
- const cfg = config.get();
687
- const recordMode = cfg.behavior?.recordRequestBreadcrumb || 'error';
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
- const cfg = config.get();
738
- const recordMode = cfg.behavior?.recordRequestBreadcrumb || 'error';
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
- const cfg = config.get();
780
- const recordMode = cfg.behavior?.recordRequestBreadcrumb || 'error';
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
- // 支持 configUrl 模式或传统 appId/dsn 模式
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
- // 支持 configUrl 模式或传统 appId/dsn 模式
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
- this.initializing = false;
1714
+ /** SDK 是否已就绪(配置完整,可以上报数据) */
1715
+ this.ready = false;
1716
+ /** 是否正在加载远程配置 */
1717
+ this.loading = false;
1718
+ /** 监控模块是否已安装 */
1719
+ this.monitorsInstalled = false;
1649
1720
  }
1650
1721
  /**
1651
1722
  * 初始化 SDK
1652
- * 如果配置了 configUrl,将先从远程获取配置(仅获取一次)
1653
- * 无需 await,SDK 内部会自动处理异步逻辑
1723
+ * 支持空配置初始化,后续通过 updateConfig 传入 configUrl 完成配置
1654
1724
  */
1655
- init(userConfig) {
1656
- if (this.initialized || this.initializing) {
1657
- if (this.initialized) {
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.initializing = true;
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.doInit(finalConfig, true);
1740
+ this.activate(finalConfig);
1673
1741
  })
1674
1742
  .catch((error) => {
1675
- this.initializing = false;
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
- // 没有 configUrl,直接同步初始化
1681
- this.doInit(userConfig, false);
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
- doInit(finalConfig, isRemoteConfig) {
1688
- // 初始化配置
1689
- config.init({
1690
- ...finalConfig,
1691
- version: finalConfig.version || SDK_VERSION,
1692
- }, isRemoteConfig);
1761
+ initBasic(userConfig) {
1693
1762
  // 初始化上下文
1694
- const maxBreadcrumbs = config.get().behavior?.maxBreadcrumbs || 20;
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
- this.initializing = false;
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 initialized', config.get());
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
- setUser(user) {
1754
- this.checkInit();
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
- updateConfig(partialConfig) {
1763
- this.checkInit();
1764
- // 如果传入了 configUrl,从远程获取配置并更新
1765
- if (partialConfig.configUrl) {
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.checkInit();
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.checkInit();
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.checkInit();
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.checkInit();
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.checkInit();
1945
+ if (!this.initialized)
1946
+ return;
1834
1947
  context.addBreadcrumb(crumb);
1835
1948
  }
1836
1949
  /**
1837
1950
  * 立即发送缓冲区数据
1838
1951
  */
1839
1952
  flush() {
1840
- this.checkInit();
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();