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.
package/dist/index.d.ts CHANGED
@@ -285,7 +285,7 @@ interface DeviceInfo {
285
285
  * ```
286
286
  */
287
287
  declare const VueMonitorPlugin: {
288
- install(app: any, options: MonitorConfig): void;
288
+ install(app: any, options?: MonitorConfig): void;
289
289
  };
290
290
 
291
291
  /**
@@ -332,7 +332,7 @@ interface ErrorBoundaryState {
332
332
  * );
333
333
  * ```
334
334
  */
335
- declare function createReactErrorBoundary(React: any, config: MonitorConfig): any;
335
+ declare function createReactErrorBoundary(React: any, config?: MonitorConfig): any;
336
336
  /**
337
337
  * React Hook: 用于手动上报错误
338
338
  *
@@ -361,18 +361,32 @@ declare function createUseMonitorError(React: any): () => any;
361
361
  * 监控 SDK 主类
362
362
  */
363
363
  declare class MonitorSDK {
364
+ /** SDK 是否已初始化(基础初始化,可能还没有完整配置) */
364
365
  private initialized;
365
- private initializing;
366
+ /** SDK 是否已就绪(配置完整,可以上报数据) */
367
+ private ready;
368
+ /** 是否正在加载远程配置 */
369
+ private loading;
370
+ /** 监控模块是否已安装 */
371
+ private monitorsInstalled;
366
372
  /**
367
373
  * 初始化 SDK
368
- * 如果配置了 configUrl,将先从远程获取配置(仅获取一次)
369
- * 无需 await,SDK 内部会自动处理异步逻辑
374
+ * 支持空配置初始化,后续通过 updateConfig 传入 configUrl 完成配置
370
375
  */
371
- init(userConfig: MonitorConfig): void;
376
+ init(userConfig?: MonitorConfig): void;
372
377
  /**
373
- * 执行实际的初始化逻辑
378
+ * 基础初始化(安装监控模块,但不开始上报)
374
379
  */
375
- private doInit;
380
+ private initBasic;
381
+ /**
382
+ * 激活 SDK(配置完整后调用)
383
+ */
384
+ private activate;
385
+ /**
386
+ * 动态更新配置
387
+ * 如果传入 configUrl,会从远程获取配置并激活 SDK
388
+ */
389
+ updateConfig(partialConfig: Partial<MonitorConfig>): void;
376
390
  /**
377
391
  * 从远程获取配置
378
392
  */
@@ -382,19 +396,17 @@ declare class MonitorSDK {
382
396
  */
383
397
  private mergeUserConfig;
384
398
  /**
385
- * 设置用户信息
399
+ * 检查 SDK 是否就绪
386
400
  */
387
- setUser(user: UserInfo): void;
401
+ isReady(): boolean;
388
402
  /**
389
- * 动态更新配置
390
- * @description 可以在运行时更新 SDK 配置,如 debug、sampling、report 等
391
- * 如果传入 configUrl,会从远程获取配置并合并
403
+ * 设置用户信息
392
404
  */
393
- updateConfig(partialConfig: Partial<MonitorConfig>): void;
405
+ setUser(user: UserInfo): void;
394
406
  /**
395
407
  * 获取当前配置
396
408
  */
397
- getConfig(): MonitorConfig;
409
+ getConfig(): MonitorConfig | null;
398
410
  /**
399
411
  * 手动上报错误
400
412
  */
@@ -419,10 +431,6 @@ declare class MonitorSDK {
419
431
  * 获取 SDK 版本
420
432
  */
421
433
  getVersion(): string;
422
- /**
423
- * 检查是否已初始化
424
- */
425
- private checkInit;
426
434
  }
427
435
  declare const monitor: MonitorSDK;
428
436
 
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 = {
@@ -1435,13 +1494,8 @@ function installBehaviorMonitor() {
1435
1494
  * ```
1436
1495
  */
1437
1496
  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(如果尚未初始化)
1497
+ install(app, options = {}) {
1498
+ // 初始化 SDK(支持空配置,后续通过 updateConfig 激活)
1445
1499
  try {
1446
1500
  monitor.init(options);
1447
1501
  }
@@ -1537,18 +1591,8 @@ const VueMonitorPlugin = {
1537
1591
  * );
1538
1592
  * ```
1539
1593
  */
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(如果尚未初始化)
1594
+ function createReactErrorBoundary(React, config = {}) {
1595
+ // 初始化 SDK(支持空配置,后续通过 updateConfig 激活)
1552
1596
  try {
1553
1597
  monitor.init(config);
1554
1598
  }
@@ -1644,64 +1688,131 @@ const SDK_VERSION = '1.0.0';
1644
1688
  */
1645
1689
  class MonitorSDK {
1646
1690
  constructor() {
1691
+ /** SDK 是否已初始化(基础初始化,可能还没有完整配置) */
1647
1692
  this.initialized = false;
1648
- this.initializing = false;
1693
+ /** SDK 是否已就绪(配置完整,可以上报数据) */
1694
+ this.ready = false;
1695
+ /** 是否正在加载远程配置 */
1696
+ this.loading = false;
1697
+ /** 监控模块是否已安装 */
1698
+ this.monitorsInstalled = false;
1649
1699
  }
1650
1700
  /**
1651
1701
  * 初始化 SDK
1652
- * 如果配置了 configUrl,将先从远程获取配置(仅获取一次)
1653
- * 无需 await,SDK 内部会自动处理异步逻辑
1702
+ * 支持空配置初始化,后续通过 updateConfig 传入 configUrl 完成配置
1654
1703
  */
1655
- init(userConfig) {
1656
- if (this.initialized || this.initializing) {
1657
- if (this.initialized) {
1658
- console.warn('[Monitor] SDK already initialized');
1659
- }
1704
+ init(userConfig = {}) {
1705
+ if (this.initialized) {
1706
+ console.warn('[Monitor] SDK already initialized');
1660
1707
  return;
1661
1708
  }
1662
- // 如果配置了 configUrl,异步获取远程配置后再初始化
1709
+ // 如果配置了 configUrl,异步获取远程配置
1663
1710
  if (userConfig.configUrl) {
1664
- this.initializing = true;
1711
+ this.loading = true;
1712
+ this.initBasic(userConfig);
1665
1713
  this.fetchRemoteConfig(userConfig.configUrl)
1666
1714
  .then((remoteConfig) => {
1667
- // 远程配置作为基础,用户本地配置覆盖远程配置
1668
1715
  const finalConfig = this.mergeUserConfig(remoteConfig, userConfig);
1669
1716
  if (userConfig.debug) {
1670
1717
  console.log('[Monitor] Remote config loaded:', remoteConfig);
1671
1718
  }
1672
- this.doInit(finalConfig, true);
1719
+ this.activate(finalConfig);
1673
1720
  })
1674
1721
  .catch((error) => {
1675
- this.initializing = false;
1722
+ this.loading = false;
1676
1723
  console.error('[Monitor] Failed to fetch remote config:', error);
1677
1724
  });
1678
1725
  }
1726
+ else if (userConfig.appId && userConfig.dsn) {
1727
+ // 有完整配置,直接激活
1728
+ this.initBasic(userConfig);
1729
+ this.activate(userConfig);
1730
+ }
1679
1731
  else {
1680
- // 没有 configUrl,直接同步初始化
1681
- this.doInit(userConfig, false);
1732
+ // 空配置或不完整配置,只做基础初始化,等待后续 updateConfig
1733
+ this.initBasic(userConfig);
1734
+ console.log('[Monitor] SDK initialized in pending mode. Call updateConfig({configUrl}) to activate.');
1682
1735
  }
1683
1736
  }
1684
1737
  /**
1685
- * 执行实际的初始化逻辑
1738
+ * 基础初始化(安装监控模块,但不开始上报)
1686
1739
  */
1687
- doInit(finalConfig, isRemoteConfig) {
1688
- // 初始化配置
1689
- config.init({
1690
- ...finalConfig,
1691
- version: finalConfig.version || SDK_VERSION,
1692
- }, isRemoteConfig);
1740
+ initBasic(userConfig) {
1693
1741
  // 初始化上下文
1694
- const maxBreadcrumbs = config.get().behavior?.maxBreadcrumbs || 20;
1742
+ const maxBreadcrumbs = userConfig.behavior?.maxBreadcrumbs || 20;
1695
1743
  context.init(maxBreadcrumbs);
1744
+ // 安装监控模块(早期捕获的数据会被缓存)
1745
+ if (!this.monitorsInstalled) {
1746
+ installErrorHandlers();
1747
+ installPerformanceMonitor();
1748
+ installBehaviorMonitor();
1749
+ this.monitorsInstalled = true;
1750
+ }
1696
1751
  this.initialized = true;
1697
- this.initializing = false;
1752
+ if (userConfig.debug) {
1753
+ console.log('[Monitor] SDK basic initialized (monitors installed, waiting for config)');
1754
+ }
1755
+ }
1756
+ /**
1757
+ * 激活 SDK(配置完整后调用)
1758
+ */
1759
+ activate(finalConfig) {
1760
+ // 初始化配置管理器
1761
+ config.init({
1762
+ ...finalConfig,
1763
+ version: finalConfig.version || SDK_VERSION,
1764
+ }, true);
1765
+ this.ready = true;
1766
+ this.loading = false;
1767
+ // 通知 reporter 可以开始上报(会自动发送缓存的早期数据)
1768
+ reporter.setReady(true);
1698
1769
  if (config.get().debug) {
1699
- console.log('[Monitor] SDK initialized', config.get());
1770
+ console.log('[Monitor] SDK activated with config:', config.get());
1771
+ }
1772
+ }
1773
+ /**
1774
+ * 动态更新配置
1775
+ * 如果传入 configUrl,会从远程获取配置并激活 SDK
1776
+ */
1777
+ updateConfig(partialConfig) {
1778
+ // 如果传入了 configUrl,从远程获取配置
1779
+ if (partialConfig.configUrl) {
1780
+ console.log('[Monitor] Fetching remote config from:', partialConfig.configUrl);
1781
+ this.loading = true;
1782
+ this.fetchRemoteConfig(partialConfig.configUrl)
1783
+ .then((remoteConfig) => {
1784
+ const mergedConfig = this.mergeUserConfig(remoteConfig, partialConfig);
1785
+ // 移除 configUrl
1786
+ delete mergedConfig.configUrl;
1787
+ if (this.ready) {
1788
+ // 已就绪,更新配置
1789
+ config.update(mergedConfig);
1790
+ this.loading = false;
1791
+ if (config.get().debug) {
1792
+ console.log('[Monitor] Config updated from remote:', mergedConfig);
1793
+ }
1794
+ }
1795
+ else {
1796
+ // 未就绪,激活 SDK
1797
+ this.activate(mergedConfig);
1798
+ }
1799
+ })
1800
+ .catch((error) => {
1801
+ this.loading = false;
1802
+ console.error('[Monitor] Failed to fetch remote config:', error);
1803
+ });
1804
+ }
1805
+ else {
1806
+ // 普通配置更新
1807
+ if (!this.ready) {
1808
+ console.warn('[Monitor] SDK not ready. Please provide configUrl or (appId + dsn) first.');
1809
+ return;
1810
+ }
1811
+ config.update(partialConfig);
1812
+ if (config.get().debug) {
1813
+ console.log('[Monitor] Config updated', partialConfig);
1814
+ }
1700
1815
  }
1701
- // 安装监控模块
1702
- installErrorHandlers();
1703
- installPerformanceMonitor();
1704
- installBehaviorMonitor();
1705
1816
  }
1706
1817
  /**
1707
1818
  * 从远程获取配置
@@ -1727,7 +1838,7 @@ class MonitorSDK {
1727
1838
  if (Object.prototype.hasOwnProperty.call(local, key)) {
1728
1839
  const localValue = local[key];
1729
1840
  const remoteValue = remote[key];
1730
- // 跳过 configUrl,不需要传递给最终配置
1841
+ // 跳过 configUrl
1731
1842
  if (key === 'configUrl')
1732
1843
  continue;
1733
1844
  if (localValue !== null &&
@@ -1748,55 +1859,33 @@ class MonitorSDK {
1748
1859
  return result;
1749
1860
  }
1750
1861
  /**
1751
- * 设置用户信息
1862
+ * 检查 SDK 是否就绪
1752
1863
  */
1753
- setUser(user) {
1754
- this.checkInit();
1755
- config.setUser(user);
1864
+ isReady() {
1865
+ return this.ready;
1756
1866
  }
1757
1867
  /**
1758
- * 动态更新配置
1759
- * @description 可以在运行时更新 SDK 配置,如 debug、sampling、report 等
1760
- * 如果传入 configUrl,会从远程获取配置并合并
1868
+ * 设置用户信息
1761
1869
  */
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
- }
1870
+ setUser(user) {
1871
+ if (!this.ready)
1872
+ return;
1873
+ config.setUser(user);
1787
1874
  }
1788
1875
  /**
1789
1876
  * 获取当前配置
1790
1877
  */
1791
1878
  getConfig() {
1792
- this.checkInit();
1879
+ if (!this.ready)
1880
+ return null;
1793
1881
  return config.get();
1794
1882
  }
1795
1883
  /**
1796
1884
  * 手动上报错误
1797
1885
  */
1798
1886
  captureError(error, extra) {
1799
- this.checkInit();
1887
+ if (!this.ready)
1888
+ return;
1800
1889
  const errorData = {
1801
1890
  type: 'js_error',
1802
1891
  message: typeof error === 'string' ? error : error.message,
@@ -1809,7 +1898,8 @@ class MonitorSDK {
1809
1898
  * 手动上报性能数据
1810
1899
  */
1811
1900
  capturePerformance(metrics) {
1812
- this.checkInit();
1901
+ if (!this.ready)
1902
+ return;
1813
1903
  reporter.reportPerformance({
1814
1904
  type: 'performance',
1815
1905
  metrics,
@@ -1819,7 +1909,8 @@ class MonitorSDK {
1819
1909
  * 手动上报行为数据
1820
1910
  */
1821
1911
  captureBehavior(action, data) {
1822
- this.checkInit();
1912
+ if (!this.ready)
1913
+ return;
1823
1914
  reporter.reportBehavior({
1824
1915
  type: 'behavior',
1825
1916
  action,
@@ -1830,14 +1921,16 @@ class MonitorSDK {
1830
1921
  * 添加面包屑
1831
1922
  */
1832
1923
  addBreadcrumb(crumb) {
1833
- this.checkInit();
1924
+ if (!this.initialized)
1925
+ return;
1834
1926
  context.addBreadcrumb(crumb);
1835
1927
  }
1836
1928
  /**
1837
1929
  * 立即发送缓冲区数据
1838
1930
  */
1839
1931
  flush() {
1840
- this.checkInit();
1932
+ if (!this.ready)
1933
+ return;
1841
1934
  reporter.flush();
1842
1935
  }
1843
1936
  /**
@@ -1846,14 +1939,6 @@ class MonitorSDK {
1846
1939
  getVersion() {
1847
1940
  return SDK_VERSION;
1848
1941
  }
1849
- /**
1850
- * 检查是否已初始化
1851
- */
1852
- checkInit() {
1853
- if (!this.initialized) {
1854
- throw new Error('[Monitor] SDK not initialized. Please call init() first.');
1855
- }
1856
- }
1857
1942
  }
1858
1943
  // 导出单例
1859
1944
  const monitor = new MonitorSDK();