sobey-monitor-sdk 1.1.2 → 1.1.3

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.
@@ -9,7 +9,6 @@ declare class ConfigManager {
9
9
  private config;
10
10
  /**
11
11
  * 初始化配置
12
- * @param skipValidation 是否跳过必填校验(用于远程配置已获取 appId/dsn 的场景)
13
12
  */
14
13
  init(userConfig: MonitorConfig, skipValidation?: boolean): void;
15
14
  /**
@@ -43,7 +43,7 @@ export interface ErrorBoundaryState {
43
43
  * );
44
44
  * ```
45
45
  */
46
- export declare function createReactErrorBoundary(React: any, config: MonitorConfig): any;
46
+ export declare function createReactErrorBoundary(React: any, config?: MonitorConfig): any;
47
47
  /**
48
48
  * React Hook: 用于手动上报错误
49
49
  *
@@ -16,6 +16,6 @@ import type { MonitorConfig } from '../types';
16
16
  * ```
17
17
  */
18
18
  export declare const VueMonitorPlugin: {
19
- install(app: any, options: MonitorConfig): void;
19
+ install(app: any, options?: MonitorConfig): void;
20
20
  };
21
21
  export default VueMonitorPlugin;
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 (not found in remote config)');
56
+ throw new Error('[Monitor] appId is required');
70
57
  }
71
58
  if (!userConfig.dsn) {
72
- throw new Error('[Monitor] dsn is required (not found in remote config)');
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 = {
@@ -1439,13 +1498,8 @@ function installBehaviorMonitor() {
1439
1498
  * ```
1440
1499
  */
1441
1500
  const VueMonitorPlugin = {
1442
- install(app, options) {
1443
- // 支持 configUrl 模式或传统 appId/dsn 模式
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(如果尚未初始化)
1501
+ install(app, options = {}) {
1502
+ // 初始化 SDK(支持空配置,后续通过 updateConfig 激活)
1449
1503
  try {
1450
1504
  monitor.init(options);
1451
1505
  }
@@ -1541,18 +1595,8 @@ const VueMonitorPlugin = {
1541
1595
  * );
1542
1596
  * ```
1543
1597
  */
1544
- function createReactErrorBoundary(React, config) {
1545
- // 支持 configUrl 模式或传统 appId/dsn 模式
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(如果尚未初始化)
1598
+ function createReactErrorBoundary(React, config = {}) {
1599
+ // 初始化 SDK(支持空配置,后续通过 updateConfig 激活)
1556
1600
  try {
1557
1601
  monitor.init(config);
1558
1602
  }
@@ -1648,64 +1692,131 @@ const SDK_VERSION = '1.0.0';
1648
1692
  */
1649
1693
  class MonitorSDK {
1650
1694
  constructor() {
1695
+ /** SDK 是否已初始化(基础初始化,可能还没有完整配置) */
1651
1696
  this.initialized = false;
1652
- this.initializing = false;
1697
+ /** SDK 是否已就绪(配置完整,可以上报数据) */
1698
+ this.ready = false;
1699
+ /** 是否正在加载远程配置 */
1700
+ this.loading = false;
1701
+ /** 监控模块是否已安装 */
1702
+ this.monitorsInstalled = false;
1653
1703
  }
1654
1704
  /**
1655
1705
  * 初始化 SDK
1656
- * 如果配置了 configUrl,将先从远程获取配置(仅获取一次)
1657
- * 无需 await,SDK 内部会自动处理异步逻辑
1706
+ * 支持空配置初始化,后续通过 updateConfig 传入 configUrl 完成配置
1658
1707
  */
1659
- init(userConfig) {
1660
- if (this.initialized || this.initializing) {
1661
- if (this.initialized) {
1662
- console.warn('[Monitor] SDK already initialized');
1663
- }
1708
+ init(userConfig = {}) {
1709
+ if (this.initialized) {
1710
+ console.warn('[Monitor] SDK already initialized');
1664
1711
  return;
1665
1712
  }
1666
- // 如果配置了 configUrl,异步获取远程配置后再初始化
1713
+ // 如果配置了 configUrl,异步获取远程配置
1667
1714
  if (userConfig.configUrl) {
1668
- this.initializing = true;
1715
+ this.loading = true;
1716
+ this.initBasic(userConfig);
1669
1717
  this.fetchRemoteConfig(userConfig.configUrl)
1670
1718
  .then((remoteConfig) => {
1671
- // 远程配置作为基础,用户本地配置覆盖远程配置
1672
1719
  const finalConfig = this.mergeUserConfig(remoteConfig, userConfig);
1673
1720
  if (userConfig.debug) {
1674
1721
  console.log('[Monitor] Remote config loaded:', remoteConfig);
1675
1722
  }
1676
- this.doInit(finalConfig, true);
1723
+ this.activate(finalConfig);
1677
1724
  })
1678
1725
  .catch((error) => {
1679
- this.initializing = false;
1726
+ this.loading = false;
1680
1727
  console.error('[Monitor] Failed to fetch remote config:', error);
1681
1728
  });
1682
1729
  }
1730
+ else if (userConfig.appId && userConfig.dsn) {
1731
+ // 有完整配置,直接激活
1732
+ this.initBasic(userConfig);
1733
+ this.activate(userConfig);
1734
+ }
1683
1735
  else {
1684
- // 没有 configUrl,直接同步初始化
1685
- this.doInit(userConfig, false);
1736
+ // 空配置或不完整配置,只做基础初始化,等待后续 updateConfig
1737
+ this.initBasic(userConfig);
1738
+ console.log('[Monitor] SDK initialized in pending mode. Call updateConfig({configUrl}) to activate.');
1686
1739
  }
1687
1740
  }
1688
1741
  /**
1689
- * 执行实际的初始化逻辑
1742
+ * 基础初始化(安装监控模块,但不开始上报)
1690
1743
  */
1691
- doInit(finalConfig, isRemoteConfig) {
1692
- // 初始化配置
1693
- config.init({
1694
- ...finalConfig,
1695
- version: finalConfig.version || SDK_VERSION,
1696
- }, isRemoteConfig);
1744
+ initBasic(userConfig) {
1697
1745
  // 初始化上下文
1698
- const maxBreadcrumbs = config.get().behavior?.maxBreadcrumbs || 20;
1746
+ const maxBreadcrumbs = userConfig.behavior?.maxBreadcrumbs || 20;
1699
1747
  context.init(maxBreadcrumbs);
1748
+ // 安装监控模块(早期捕获的数据会被缓存)
1749
+ if (!this.monitorsInstalled) {
1750
+ installErrorHandlers();
1751
+ installPerformanceMonitor();
1752
+ installBehaviorMonitor();
1753
+ this.monitorsInstalled = true;
1754
+ }
1700
1755
  this.initialized = true;
1701
- this.initializing = false;
1756
+ if (userConfig.debug) {
1757
+ console.log('[Monitor] SDK basic initialized (monitors installed, waiting for config)');
1758
+ }
1759
+ }
1760
+ /**
1761
+ * 激活 SDK(配置完整后调用)
1762
+ */
1763
+ activate(finalConfig) {
1764
+ // 初始化配置管理器
1765
+ config.init({
1766
+ ...finalConfig,
1767
+ version: finalConfig.version || SDK_VERSION,
1768
+ }, true);
1769
+ this.ready = true;
1770
+ this.loading = false;
1771
+ // 通知 reporter 可以开始上报(会自动发送缓存的早期数据)
1772
+ reporter.setReady(true);
1702
1773
  if (config.get().debug) {
1703
- console.log('[Monitor] SDK initialized', config.get());
1774
+ console.log('[Monitor] SDK activated with config:', config.get());
1775
+ }
1776
+ }
1777
+ /**
1778
+ * 动态更新配置
1779
+ * 如果传入 configUrl,会从远程获取配置并激活 SDK
1780
+ */
1781
+ updateConfig(partialConfig) {
1782
+ // 如果传入了 configUrl,从远程获取配置
1783
+ if (partialConfig.configUrl) {
1784
+ console.log('[Monitor] Fetching remote config from:', partialConfig.configUrl);
1785
+ this.loading = true;
1786
+ this.fetchRemoteConfig(partialConfig.configUrl)
1787
+ .then((remoteConfig) => {
1788
+ const mergedConfig = this.mergeUserConfig(remoteConfig, partialConfig);
1789
+ // 移除 configUrl
1790
+ delete mergedConfig.configUrl;
1791
+ if (this.ready) {
1792
+ // 已就绪,更新配置
1793
+ config.update(mergedConfig);
1794
+ this.loading = false;
1795
+ if (config.get().debug) {
1796
+ console.log('[Monitor] Config updated from remote:', mergedConfig);
1797
+ }
1798
+ }
1799
+ else {
1800
+ // 未就绪,激活 SDK
1801
+ this.activate(mergedConfig);
1802
+ }
1803
+ })
1804
+ .catch((error) => {
1805
+ this.loading = false;
1806
+ console.error('[Monitor] Failed to fetch remote config:', error);
1807
+ });
1808
+ }
1809
+ else {
1810
+ // 普通配置更新
1811
+ if (!this.ready) {
1812
+ console.warn('[Monitor] SDK not ready. Please provide configUrl or (appId + dsn) first.');
1813
+ return;
1814
+ }
1815
+ config.update(partialConfig);
1816
+ if (config.get().debug) {
1817
+ console.log('[Monitor] Config updated', partialConfig);
1818
+ }
1704
1819
  }
1705
- // 安装监控模块
1706
- installErrorHandlers();
1707
- installPerformanceMonitor();
1708
- installBehaviorMonitor();
1709
1820
  }
1710
1821
  /**
1711
1822
  * 从远程获取配置
@@ -1731,7 +1842,7 @@ class MonitorSDK {
1731
1842
  if (Object.prototype.hasOwnProperty.call(local, key)) {
1732
1843
  const localValue = local[key];
1733
1844
  const remoteValue = remote[key];
1734
- // 跳过 configUrl,不需要传递给最终配置
1845
+ // 跳过 configUrl
1735
1846
  if (key === 'configUrl')
1736
1847
  continue;
1737
1848
  if (localValue !== null &&
@@ -1752,55 +1863,33 @@ class MonitorSDK {
1752
1863
  return result;
1753
1864
  }
1754
1865
  /**
1755
- * 设置用户信息
1866
+ * 检查 SDK 是否就绪
1756
1867
  */
1757
- setUser(user) {
1758
- this.checkInit();
1759
- config.setUser(user);
1868
+ isReady() {
1869
+ return this.ready;
1760
1870
  }
1761
1871
  /**
1762
- * 动态更新配置
1763
- * @description 可以在运行时更新 SDK 配置,如 debug、sampling、report 等
1764
- * 如果传入 configUrl,会从远程获取配置并合并
1872
+ * 设置用户信息
1765
1873
  */
1766
- updateConfig(partialConfig) {
1767
- this.checkInit();
1768
- // 如果传入了 configUrl,从远程获取配置并更新
1769
- if (partialConfig.configUrl) {
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
- }
1874
+ setUser(user) {
1875
+ if (!this.ready)
1876
+ return;
1877
+ config.setUser(user);
1791
1878
  }
1792
1879
  /**
1793
1880
  * 获取当前配置
1794
1881
  */
1795
1882
  getConfig() {
1796
- this.checkInit();
1883
+ if (!this.ready)
1884
+ return null;
1797
1885
  return config.get();
1798
1886
  }
1799
1887
  /**
1800
1888
  * 手动上报错误
1801
1889
  */
1802
1890
  captureError(error, extra) {
1803
- this.checkInit();
1891
+ if (!this.ready)
1892
+ return;
1804
1893
  const errorData = {
1805
1894
  type: 'js_error',
1806
1895
  message: typeof error === 'string' ? error : error.message,
@@ -1813,7 +1902,8 @@ class MonitorSDK {
1813
1902
  * 手动上报性能数据
1814
1903
  */
1815
1904
  capturePerformance(metrics) {
1816
- this.checkInit();
1905
+ if (!this.ready)
1906
+ return;
1817
1907
  reporter.reportPerformance({
1818
1908
  type: 'performance',
1819
1909
  metrics,
@@ -1823,7 +1913,8 @@ class MonitorSDK {
1823
1913
  * 手动上报行为数据
1824
1914
  */
1825
1915
  captureBehavior(action, data) {
1826
- this.checkInit();
1916
+ if (!this.ready)
1917
+ return;
1827
1918
  reporter.reportBehavior({
1828
1919
  type: 'behavior',
1829
1920
  action,
@@ -1834,14 +1925,16 @@ class MonitorSDK {
1834
1925
  * 添加面包屑
1835
1926
  */
1836
1927
  addBreadcrumb(crumb) {
1837
- this.checkInit();
1928
+ if (!this.initialized)
1929
+ return;
1838
1930
  context.addBreadcrumb(crumb);
1839
1931
  }
1840
1932
  /**
1841
1933
  * 立即发送缓冲区数据
1842
1934
  */
1843
1935
  flush() {
1844
- this.checkInit();
1936
+ if (!this.ready)
1937
+ return;
1845
1938
  reporter.flush();
1846
1939
  }
1847
1940
  /**
@@ -1850,14 +1943,6 @@ class MonitorSDK {
1850
1943
  getVersion() {
1851
1944
  return SDK_VERSION;
1852
1945
  }
1853
- /**
1854
- * 检查是否已初始化
1855
- */
1856
- checkInit() {
1857
- if (!this.initialized) {
1858
- throw new Error('[Monitor] SDK not initialized. Please call init() first.');
1859
- }
1860
- }
1861
1946
  }
1862
1947
  // 导出单例
1863
1948
  const monitor = new MonitorSDK();