sobey-monitor-sdk 1.0.2 → 1.0.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/README.md CHANGED
@@ -44,6 +44,68 @@ monitor.init({
44
44
  ### `monitor.flush()`
45
45
  立即上报缓冲区中的数据。
46
46
 
47
+ ## 框架集成
48
+
49
+ ### Vue 3
50
+
51
+ 使用 `VueMonitorPlugin` 插件,可以捕获 Vue 组件内的渲染错误:
52
+
53
+ ```typescript
54
+ import { createApp } from 'vue';
55
+ import App from './App.vue';
56
+ import { VueMonitorPlugin } from 'sobey-monitor-sdk';
57
+
58
+ const app = createApp(App);
59
+
60
+ app.use(VueMonitorPlugin, {
61
+ appId: 'your-app-id',
62
+ dsn: 'http://localhost:3000/api/report',
63
+ debug: true,
64
+ });
65
+
66
+ app.mount('#app');
67
+ ```
68
+
69
+ ### React
70
+
71
+ 使用 `createReactErrorBoundary` 创建错误边界组件,包裹根组件以捕获渲染错误:
72
+
73
+ ```tsx
74
+ import React from 'react';
75
+ import ReactDOM from 'react-dom';
76
+ import App from './App';
77
+ import { createReactErrorBoundary } from 'sobey-monitor-sdk';
78
+
79
+ // 创建错误边界组件
80
+ const ErrorBoundary = createReactErrorBoundary(React, {
81
+ appId: 'your-app-id',
82
+ dsn: 'http://localhost:3000/api/report',
83
+ debug: true,
84
+ });
85
+
86
+ ReactDOM.render(
87
+ <ErrorBoundary fallback={<h1>页面出错了</h1>}>
88
+ <App />
89
+ </ErrorBoundary>,
90
+ document.getElementById('root')
91
+ );
92
+ ```
93
+
94
+ **注意**:框架集成会自动初始化 SDK,无需再手动调用 `monitor.init()`。
95
+
96
+ ## 配置项
97
+
98
+ | 配置项 | 类型 | 默认值 | 说明 |
99
+ |--------|------|--------|------|
100
+ | `appId` | string | 必填 | 应用唯一标识 |
101
+ | `dsn` | string | 必填 | 数据上报地址 |
102
+ | `debug` | boolean | false | 调试模式 |
103
+ | `sampling.error` | number | 1 | 错误采样率 (0-1) |
104
+ | `sampling.performance` | number | 1 | 性能采样率 (0-1) |
105
+ | `sampling.behavior` | number | 1 | 行为采样率 (0-1) |
106
+ | `report.maxBufferSize` | number | 10 | 缓冲区最大数量 |
107
+ | `report.flushInterval` | number | 5000 | 上报间隔 (ms) |
108
+
47
109
  ## 构建
48
110
 
49
111
  ```bash
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 框架集成模块
3
+ * 导出 Vue 和 React 的集成组件
4
+ */
5
+ export { VueMonitorPlugin } from './vue';
6
+ export { createReactErrorBoundary, createUseMonitorError } from './react';
7
+ export type { ErrorBoundaryProps, ErrorBoundaryState } from './react';
@@ -0,0 +1,74 @@
1
+ import type { MonitorConfig } from '../types';
2
+ /**
3
+ * React ErrorBoundary 组件 Props
4
+ */
5
+ export interface ErrorBoundaryProps {
6
+ /** 发生错误时显示的降级 UI */
7
+ fallback?: any;
8
+ /** 错误回调 */
9
+ onError?: (error: Error, errorInfo: any) => void;
10
+ /** 子组件 */
11
+ children?: any;
12
+ }
13
+ /**
14
+ * React ErrorBoundary 组件 State
15
+ */
16
+ export interface ErrorBoundaryState {
17
+ hasError: boolean;
18
+ error: Error | null;
19
+ }
20
+ /**
21
+ * 创建 React 错误边界组件
22
+ *
23
+ * @param React - React 库引用
24
+ * @param config - SDK 配置
25
+ * @returns ErrorBoundary 组件类
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * import React from 'react';
30
+ * import ReactDOM from 'react-dom';
31
+ * import { createReactErrorBoundary } from 'sobey-monitor-sdk';
32
+ *
33
+ * const ErrorBoundary = createReactErrorBoundary(React, {
34
+ * appId: 'your-app-id',
35
+ * dsn: 'http://your-server/api/report',
36
+ * });
37
+ *
38
+ * ReactDOM.render(
39
+ * <ErrorBoundary fallback={<h1>Something went wrong</h1>}>
40
+ * <App />
41
+ * </ErrorBoundary>,
42
+ * document.getElementById('root')
43
+ * );
44
+ * ```
45
+ */
46
+ export declare function createReactErrorBoundary(React: any, config: MonitorConfig): any;
47
+ /**
48
+ * React Hook: 用于手动上报错误
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * import { useMonitorError } from 'sobey-monitor-sdk';
53
+ *
54
+ * function MyComponent() {
55
+ * const reportError = useMonitorError();
56
+ *
57
+ * const handleClick = async () => {
58
+ * try {
59
+ * await riskyOperation();
60
+ * } catch (error) {
61
+ * reportError(error, { action: 'riskyOperation' });
62
+ * }
63
+ * };
64
+ *
65
+ * return <button onClick={handleClick}>Click</button>;
66
+ * }
67
+ * ```
68
+ */
69
+ export declare function createUseMonitorError(React: any): () => any;
70
+ declare const _default: {
71
+ createReactErrorBoundary: typeof createReactErrorBoundary;
72
+ createUseMonitorError: typeof createUseMonitorError;
73
+ };
74
+ export default _default;
@@ -0,0 +1,21 @@
1
+ import type { MonitorConfig } from '../types';
2
+ /**
3
+ * Vue 3 错误监控插件
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { createApp } from 'vue';
8
+ * import { VueMonitorPlugin } from 'sobey-monitor-sdk';
9
+ *
10
+ * const app = createApp(App);
11
+ * app.use(VueMonitorPlugin, {
12
+ * appId: 'your-app-id',
13
+ * dsn: 'http://your-server/api/report',
14
+ * });
15
+ * app.mount('#app');
16
+ * ```
17
+ */
18
+ export declare const VueMonitorPlugin: {
19
+ install(app: any, options: MonitorConfig): void;
20
+ };
21
+ export default VueMonitorPlugin;
package/dist/index.cjs.js CHANGED
@@ -291,10 +291,13 @@ class Sender {
291
291
  /**
292
292
  * 使用 sendBeacon 发送
293
293
  * 返回 true 表示成功加入队列,false 表示失败需要降级
294
+ * sendBeacon 发送的 Content-Type 是 text/plain,需要使用 /beacon 接口
294
295
  */
295
296
  sendByBeacon(url, payload) {
296
297
  try {
297
- const success = navigator.sendBeacon(url, payload);
298
+ // sendBeacon 需要使用 /beacon 接口,因为它发送的是 text/plain
299
+ const beaconUrl = url.replace(/\/report$/, '/beacon');
300
+ const success = navigator.sendBeacon(beaconUrl, payload);
298
301
  if (!success && config.get().debug) {
299
302
  console.warn('[Monitor] sendBeacon returned false, falling back to fetch');
300
303
  }
@@ -1362,6 +1365,223 @@ function installBehaviorMonitor() {
1362
1365
  installInputTracker();
1363
1366
  }
1364
1367
 
1368
+ /**
1369
+ * Vue 3 框架集成
1370
+ * 捕获 Vue 组件内的渲染错误
1371
+ */
1372
+ /**
1373
+ * Vue 3 错误监控插件
1374
+ *
1375
+ * @example
1376
+ * ```ts
1377
+ * import { createApp } from 'vue';
1378
+ * import { VueMonitorPlugin } from 'sobey-monitor-sdk';
1379
+ *
1380
+ * const app = createApp(App);
1381
+ * app.use(VueMonitorPlugin, {
1382
+ * appId: 'your-app-id',
1383
+ * dsn: 'http://your-server/api/report',
1384
+ * });
1385
+ * app.mount('#app');
1386
+ * ```
1387
+ */
1388
+ const VueMonitorPlugin = {
1389
+ install(app, options) {
1390
+ if (!options || !options.appId || !options.dsn) {
1391
+ console.warn('[Monitor] VueMonitorPlugin requires appId and dsn in options');
1392
+ return;
1393
+ }
1394
+ // 初始化 SDK(如果尚未初始化)
1395
+ try {
1396
+ monitor.init(options);
1397
+ }
1398
+ catch (e) {
1399
+ // SDK 可能已经初始化,忽略错误
1400
+ }
1401
+ // 保存原有的错误处理器
1402
+ const originalErrorHandler = app.config.errorHandler;
1403
+ // 设置 Vue 错误处理器
1404
+ app.config.errorHandler = (err, instance, info) => {
1405
+ // 提取错误信息
1406
+ const error = err instanceof Error ? err : new Error(String(err));
1407
+ // 获取组件名称
1408
+ let componentName = 'Unknown';
1409
+ if (instance) {
1410
+ componentName = instance.$options?.name ||
1411
+ instance.$.type?.name ||
1412
+ instance.$.type?.__name ||
1413
+ 'AnonymousComponent';
1414
+ }
1415
+ // 添加面包屑
1416
+ context.addBreadcrumb({
1417
+ type: 'framework',
1418
+ category: 'vue_error',
1419
+ data: {
1420
+ componentName,
1421
+ info,
1422
+ message: error.message,
1423
+ },
1424
+ });
1425
+ // 上报错误
1426
+ reporter.reportError({
1427
+ type: 'vue_error',
1428
+ message: error.message,
1429
+ stack: error.stack,
1430
+ componentName,
1431
+ lifecycleHook: info,
1432
+ });
1433
+ // 调用原有的错误处理器
1434
+ if (typeof originalErrorHandler === 'function') {
1435
+ originalErrorHandler(err, instance, info);
1436
+ }
1437
+ };
1438
+ // 设置警告处理器(仅在开发模式下)
1439
+ if (options.debug) {
1440
+ const originalWarnHandler = app.config.warnHandler;
1441
+ app.config.warnHandler = (msg, instance, trace) => {
1442
+ // 添加面包屑
1443
+ context.addBreadcrumb({
1444
+ type: 'console',
1445
+ category: 'vue_warning',
1446
+ data: { message: msg },
1447
+ });
1448
+ // 调用原有的警告处理器
1449
+ if (typeof originalWarnHandler === 'function') {
1450
+ originalWarnHandler(msg, instance, trace);
1451
+ }
1452
+ };
1453
+ }
1454
+ if (options.debug) {
1455
+ console.log('[Monitor] Vue plugin installed');
1456
+ }
1457
+ },
1458
+ };
1459
+
1460
+ /**
1461
+ * React 框架集成
1462
+ * 提供 ErrorBoundary 组件捕获渲染错误
1463
+ */
1464
+ /**
1465
+ * 创建 React 错误边界组件
1466
+ *
1467
+ * @param React - React 库引用
1468
+ * @param config - SDK 配置
1469
+ * @returns ErrorBoundary 组件类
1470
+ *
1471
+ * @example
1472
+ * ```tsx
1473
+ * import React from 'react';
1474
+ * import ReactDOM from 'react-dom';
1475
+ * import { createReactErrorBoundary } from 'sobey-monitor-sdk';
1476
+ *
1477
+ * const ErrorBoundary = createReactErrorBoundary(React, {
1478
+ * appId: 'your-app-id',
1479
+ * dsn: 'http://your-server/api/report',
1480
+ * });
1481
+ *
1482
+ * ReactDOM.render(
1483
+ * <ErrorBoundary fallback={<h1>Something went wrong</h1>}>
1484
+ * <App />
1485
+ * </ErrorBoundary>,
1486
+ * document.getElementById('root')
1487
+ * );
1488
+ * ```
1489
+ */
1490
+ function createReactErrorBoundary(React, config) {
1491
+ if (!config || !config.appId || !config.dsn) {
1492
+ console.warn('[Monitor] createReactErrorBoundary requires appId and dsn in config');
1493
+ // 返回一个空的组件
1494
+ return class EmptyBoundary extends React.Component {
1495
+ render() {
1496
+ return this.props.children;
1497
+ }
1498
+ };
1499
+ }
1500
+ // 初始化 SDK(如果尚未初始化)
1501
+ try {
1502
+ monitor.init(config);
1503
+ }
1504
+ catch (e) {
1505
+ // SDK 可能已经初始化,忽略错误
1506
+ }
1507
+ /**
1508
+ * React 错误边界组件
1509
+ */
1510
+ return class ErrorMonitorBoundary extends React.Component {
1511
+ constructor(props) {
1512
+ super(props);
1513
+ this.state = { hasError: false, error: null };
1514
+ }
1515
+ static getDerivedStateFromError(error) {
1516
+ return { hasError: true, error };
1517
+ }
1518
+ componentDidCatch(error, errorInfo) {
1519
+ // 获取组件堆栈
1520
+ const componentStack = errorInfo?.componentStack || '';
1521
+ // 添加面包屑
1522
+ context.addBreadcrumb({
1523
+ type: 'framework',
1524
+ category: 'react_error',
1525
+ data: {
1526
+ message: error.message,
1527
+ componentStack: componentStack.substring(0, 500),
1528
+ },
1529
+ });
1530
+ // 上报错误
1531
+ reporter.reportError({
1532
+ type: 'react_error',
1533
+ message: error.message,
1534
+ stack: error.stack,
1535
+ componentStack,
1536
+ });
1537
+ // 调用自定义错误回调
1538
+ if (typeof this.props.onError === 'function') {
1539
+ this.props.onError(error, errorInfo);
1540
+ }
1541
+ if (config.debug) {
1542
+ console.error('[Monitor] React error caught:', error);
1543
+ console.error('[Monitor] Component stack:', componentStack);
1544
+ }
1545
+ }
1546
+ render() {
1547
+ if (this.state.hasError) {
1548
+ // 显示降级 UI
1549
+ return this.props.fallback || null;
1550
+ }
1551
+ return this.props.children;
1552
+ }
1553
+ };
1554
+ }
1555
+ /**
1556
+ * React Hook: 用于手动上报错误
1557
+ *
1558
+ * @example
1559
+ * ```tsx
1560
+ * import { useMonitorError } from 'sobey-monitor-sdk';
1561
+ *
1562
+ * function MyComponent() {
1563
+ * const reportError = useMonitorError();
1564
+ *
1565
+ * const handleClick = async () => {
1566
+ * try {
1567
+ * await riskyOperation();
1568
+ * } catch (error) {
1569
+ * reportError(error, { action: 'riskyOperation' });
1570
+ * }
1571
+ * };
1572
+ *
1573
+ * return <button onClick={handleClick}>Click</button>;
1574
+ * }
1575
+ * ```
1576
+ */
1577
+ function createUseMonitorError(React) {
1578
+ return function useMonitorError() {
1579
+ return React.useCallback((error, extra) => {
1580
+ monitor.captureError(error, extra);
1581
+ }, []);
1582
+ };
1583
+ }
1584
+
1365
1585
  /**
1366
1586
  * 前端监控 SDK
1367
1587
  * @description 错误监控、性能监控、行为监控
@@ -1473,6 +1693,9 @@ class MonitorSDK {
1473
1693
  // 导出单例
1474
1694
  const monitor = new MonitorSDK();
1475
1695
 
1696
+ exports.VueMonitorPlugin = VueMonitorPlugin;
1697
+ exports.createReactErrorBoundary = createReactErrorBoundary;
1698
+ exports.createUseMonitorError = createUseMonitorError;
1476
1699
  exports.default = monitor;
1477
1700
  exports.monitor = monitor;
1478
1701
  //# sourceMappingURL=index.cjs.js.map