@xmov/avatar 2.0.0-alpha.61 → 2.0.0-alpha.63
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.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.modern.js +1 -1
- package/dist/index.modern.js.map +1 -1
- package/dist/index.module.js +1 -1
- package/dist/index.module.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/modules/sentry-reporter.d.ts +58 -0
- package/dist/types/index.d.ts +11 -2
- package/package.json +8 -6
- package/src/global.d.ts +7 -0
- package/src/index.ts +70 -3
- package/src/modules/sentry-reporter.ts +171 -0
- package/src/types/index.ts +13 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { SDKError } from 'types/error';
|
|
2
|
+
export interface SentryReporterOptions {
|
|
3
|
+
/** Traces 采样率,默认 1.0(100% 全量) */
|
|
4
|
+
tracesSampleRate?: number;
|
|
5
|
+
}
|
|
6
|
+
/** span 附加属性(兼容 Sentry SpanAttributeValue 的标量子集) */
|
|
7
|
+
type SpanData = Record<string, string | number | boolean | undefined>;
|
|
8
|
+
/**
|
|
9
|
+
* Sentry 错误上报器(隔离实例)
|
|
10
|
+
*
|
|
11
|
+
* 使用独立的 BrowserClient + Scope,不污染宿主应用的全局 Sentry 状态。
|
|
12
|
+
* 此文件仅在公有云构建(ENABLE_SENTRY=true)时被打包,
|
|
13
|
+
* 私有化构建时通过编译时常量 + 死代码消除完全移除。
|
|
14
|
+
*/
|
|
15
|
+
export declare class SentryReporter {
|
|
16
|
+
private client;
|
|
17
|
+
private scope;
|
|
18
|
+
private tracesSampleRate;
|
|
19
|
+
constructor(dsn: string, options?: SentryReporterOptions);
|
|
20
|
+
/**
|
|
21
|
+
* 上报 SDK 错误
|
|
22
|
+
*/
|
|
23
|
+
captureError(error: SDKError): void;
|
|
24
|
+
/**
|
|
25
|
+
* 在隔离 scope 上启动一个 span 并执行回调,回调返回时自动结束。
|
|
26
|
+
* 异常时 span 自动标记 error(由 startSpan 内部处理),span 数据发往本实例的 DSN。
|
|
27
|
+
*/
|
|
28
|
+
startSpan<T>(name: string, ctx: {
|
|
29
|
+
op?: string;
|
|
30
|
+
data?: SpanData;
|
|
31
|
+
}, fn: () => T): T;
|
|
32
|
+
/**
|
|
33
|
+
* 启动一个手动管理生命周期的 span(跨回调/异步时用),
|
|
34
|
+
* 返回的句柄在操作完成时调用 end() 结束 span。
|
|
35
|
+
*/
|
|
36
|
+
startInactiveSpan(name: string, ctx: {
|
|
37
|
+
op?: string;
|
|
38
|
+
data?: SpanData;
|
|
39
|
+
}): {
|
|
40
|
+
end(): void;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* 设置全局标签
|
|
44
|
+
*/
|
|
45
|
+
setTag(key: string, value: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* 设置用户信息
|
|
48
|
+
*/
|
|
49
|
+
setUser(user: {
|
|
50
|
+
id?: string;
|
|
51
|
+
ip_address?: string;
|
|
52
|
+
}): void;
|
|
53
|
+
/**
|
|
54
|
+
* 销毁 Sentry 客户端,flush 未发送的事件
|
|
55
|
+
*/
|
|
56
|
+
destroy(): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
export {};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -96,9 +96,18 @@ export interface IAvatarOptions {
|
|
|
96
96
|
*/
|
|
97
97
|
onAudioPlaybackData?(data: IAudioPlaybackData): void;
|
|
98
98
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
99
|
+
* Sentry 错误监控配置
|
|
100
|
+
* 仅在公有云构建(ENABLE_SENTRY=true)时生效,私有化构建会忽略此配置
|
|
101
|
+
* 设为 { enabled: false } 可禁用 Sentry 上报
|
|
101
102
|
*/
|
|
103
|
+
sentry?: {
|
|
104
|
+
enabled?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* 性能 Traces 采样率(0-1),默认 1.0(100% 全量)
|
|
107
|
+
* 容量受限时可下调,如 0.1(10%)
|
|
108
|
+
*/
|
|
109
|
+
tracesSampleRate?: number;
|
|
110
|
+
};
|
|
102
111
|
onFrameDataUpdate?(data: IFrameDataUpdate): void;
|
|
103
112
|
}
|
|
104
113
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmov/avatar",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.63",
|
|
4
4
|
"description": "魔珐数字人 JS-SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"source": "src/index.ts",
|
|
@@ -43,13 +43,14 @@
|
|
|
43
43
|
"!dist/agent/microphone.d.ts"
|
|
44
44
|
],
|
|
45
45
|
"scripts": {
|
|
46
|
-
"build": "pnpm run build:core && pnpm run build:agent && pnpm run build:e2e:cdn && pnpm run test:e2e:cdn && node upload-sdk.js build",
|
|
47
|
-
"build:core": "microbundle --workers --define ENV=production,VERSION=$npm_package_version --external none --tsconfig tsconfig.json",
|
|
48
|
-
"build:agent": "microbundle --entry src/agent/index.ts --output dist/agent --format modern,esm,cjs,umd --external @xmov/avatar --alias ../index=@xmov/avatar --globals @xmov/avatar=XmovAvatar --name XingyunAvatarAgentModule --tsconfig tsconfig.agent.json --no-sourcemap && node scripts/build-agent-entry.mjs",
|
|
46
|
+
"build": "pnpm run build:core && pnpm run build:agent && pnpm run build:e2e:cdn && pnpm run test:e2e:cdn && node scripts/upload-sourcemaps.mjs && node upload-sdk.js build",
|
|
47
|
+
"build:core": "microbundle --workers --define ENV=production,VERSION=$npm_package_version,ENABLE_SENTRY=true,SENTRY_DSN='https://aabed165326fc7a2a276b49aa4043be3@xmovsentry.xmov.ai/32' --external none --tsconfig tsconfig.json",
|
|
48
|
+
"build:agent": "microbundle --entry src/agent/index.ts --output dist/agent --format modern,esm,cjs,umd --external @xmov/avatar --alias ../index=@xmov/avatar --globals @xmov/avatar=XmovAvatar --name XingyunAvatarAgentModule --tsconfig tsconfig.agent.json --no-sourcemap --define ENV=production,VERSION=$npm_package_version,ENABLE_SENTRY=true,SENTRY_DSN='https://aabed165326fc7a2a276b49aa4043be3@xmovsentry.xmov.ai/32' && node scripts/build-agent-entry.mjs",
|
|
49
|
+
"build:private": "microbundle --workers --define ENV=production,VERSION=$npm_package_version,ENABLE_SENTRY=false,SENTRY_DSN='' --external @sentry/browser --tsconfig tsconfig.json && node scripts/strip-sentry-imports.mjs && pnpm run build:agent",
|
|
49
50
|
"build:e2e:cdn": "microbundle --entry src/agent/cdn-entry.ts --output cdn-dist/xmovAvatar_e2e.js --format iife --external none --workers --define ENV=production,VERSION=$npm_package_version --tsconfig tsconfig.agent.json --no-pkg-main --no-sourcemap",
|
|
50
51
|
"sdk": "pnpm --filter @xmov/avatar run --parallel --no-bail \"/^sdk:.+$/\"",
|
|
51
|
-
"sdk:core": "microbundle watch --workers --define ENV=development,VERSION=$npm_package_version --external none --tsconfig tsconfig.json",
|
|
52
|
-
"sdk:agent": "node scripts/build-agent-entry.mjs && microbundle watch --entry src/agent/index.ts --output dist/agent --format modern,esm,cjs,umd --external @xmov/avatar --alias ../index=@xmov/avatar --globals @xmov/avatar=XmovAvatar --name XingyunAvatarAgentModule --tsconfig tsconfig.agent.json",
|
|
52
|
+
"sdk:core": "microbundle watch --workers --define ENV=development,VERSION=$npm_package_version,ENABLE_SENTRY=true,SENTRY_DSN='https://aabed165326fc7a2a276b49aa4043be3@xmovsentry.xmov.ai/32' --external none --tsconfig tsconfig.json",
|
|
53
|
+
"sdk:agent": "node scripts/build-agent-entry.mjs && microbundle watch --entry src/agent/index.ts --output dist/agent --format modern,esm,cjs,umd --external @xmov/avatar --alias ../index=@xmov/avatar --globals @xmov/avatar=XmovAvatar --name XingyunAvatarAgentModule --tsconfig tsconfig.agent.json --define ENV=development,VERSION=$npm_package_version,ENABLE_SENTRY=true,SENTRY_DSN='https://aabed165326fc7a2a276b49aa4043be3@xmovsentry.xmov.ai/32'",
|
|
53
54
|
"app": "cd app/ && npm run dev",
|
|
54
55
|
"app:lan": "pnpm --dir app dev:lan",
|
|
55
56
|
"test:agent": "node scripts/run-agent-tests.mjs",
|
|
@@ -95,6 +96,7 @@
|
|
|
95
96
|
"ws": "^8.21.1"
|
|
96
97
|
},
|
|
97
98
|
"dependencies": {
|
|
99
|
+
"@sentry/browser": "^10.69.0",
|
|
98
100
|
"blueimp-md5": "^2.19.0",
|
|
99
101
|
"protobufjs": "^7.5.4"
|
|
100
102
|
},
|
package/src/global.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
// src/global.d.ts
|
|
2
|
+
|
|
3
|
+
// 编译时常量声明(通过 microbundle --define 注入)
|
|
4
|
+
declare const VERSION: string;
|
|
5
|
+
declare const ENV: string;
|
|
6
|
+
declare const ENABLE_SENTRY: boolean;
|
|
7
|
+
declare const SENTRY_DSN: string;
|
|
8
|
+
|
|
2
9
|
// 1. 声明全局 protobuf 对象(适配 protobufjs UMD 格式)
|
|
3
10
|
declare namespace globalThis {
|
|
4
11
|
const protobuf: {
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import Ttsa from "./control/ttsa";
|
|
|
8
8
|
import { DebugOverlay } from "./view/DebugOverlay";
|
|
9
9
|
import NetworkMonitor from './modules/network';
|
|
10
10
|
import Errors from './modules/error-handle';
|
|
11
|
+
import { SentryReporter } from './modules/sentry-reporter';
|
|
11
12
|
|
|
12
13
|
import { performanceConstant } from "./utils/perfermance";
|
|
13
14
|
import './utils/logger.js';
|
|
@@ -47,6 +48,9 @@ export default class XmovAvatar {
|
|
|
47
48
|
private ttsa: Ttsa | null = null;
|
|
48
49
|
|
|
49
50
|
private debugOverlay: DebugOverlay | null = null;
|
|
51
|
+
private sentryReporter: SentryReporter | null = null;
|
|
52
|
+
/** 首帧 span 句柄(init 时启动,首次 BODY 帧到达时结束) */
|
|
53
|
+
private firstFrameSpan: { end(): void } | null = null;
|
|
50
54
|
|
|
51
55
|
private _offlineTimer = -1
|
|
52
56
|
private readonly _offlineInterval = 300
|
|
@@ -570,6 +574,25 @@ export default class XmovAvatar {
|
|
|
570
574
|
return;
|
|
571
575
|
}
|
|
572
576
|
|
|
577
|
+
// 初始化 Sentry 错误监控(编译时常量 + 运行时配置双重控制)
|
|
578
|
+
// 编译时 ENABLE_SENTRY 决定代码是否打包,运行时 sentry.enabled 决定是否启用
|
|
579
|
+
// 提前到资源加载之前创建,使 init / resource_load span 可覆盖整个初始化过程
|
|
580
|
+
if (ENABLE_SENTRY && this.options.sentry?.enabled) {
|
|
581
|
+
try {
|
|
582
|
+
this.sentryReporter = new SentryReporter(SENTRY_DSN, {
|
|
583
|
+
tracesSampleRate: this.options.sentry?.tracesSampleRate,
|
|
584
|
+
});
|
|
585
|
+
this.sentryReporter.setTag('app_id', this.options.appId || '');
|
|
586
|
+
this.sentryReporter.setTag('env', this._env);
|
|
587
|
+
} catch (e) {
|
|
588
|
+
(window as any).avatarSDKLogger.warn(this.TAG, 'Sentry 初始化失败', e);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
// sdk.init:初始化总耗时(含资源加载、连接就绪)
|
|
592
|
+
const initSpan = this.sentryReporter?.startInactiveSpan('sdk.init', { op: 'init' });
|
|
593
|
+
// sdk.first_frame:从初始化到首个 body 帧到达(handleMessage 首次 BODY 帧时结束)
|
|
594
|
+
this.firstFrameSpan = this.sentryReporter?.startInactiveSpan('sdk.first_frame', { op: 'render' }) ?? null;
|
|
595
|
+
|
|
573
596
|
const { onDownloadProgress, initModel } = params;
|
|
574
597
|
this.onDownloadProgress = (progress: number) => {
|
|
575
598
|
if(progress === 15) {
|
|
@@ -586,17 +609,24 @@ export default class XmovAvatar {
|
|
|
586
609
|
this.pendingInvisibleMode = true;
|
|
587
610
|
}
|
|
588
611
|
|
|
589
|
-
//
|
|
612
|
+
// 加载资源(sdk.resource_load:资源包下载/解析耗时)
|
|
590
613
|
(window as any).performanceTracker.markStart(performanceConstant.load_resource);
|
|
591
614
|
(window as any).performanceTracker.markStart(performanceConstant.first_avatar_render);
|
|
592
615
|
(window as any).performanceTracker.markStart(performanceConstant.first_webgl_render);
|
|
593
|
-
const
|
|
616
|
+
const downloadProgress = this.onDownloadProgress;
|
|
617
|
+
const sessionInfo = this.sentryReporter
|
|
618
|
+
? await this.sentryReporter.startSpan('sdk.resource_load', { op: 'resource.load' }, () =>
|
|
619
|
+
this.resourceManager.load(downloadProgress.bind(this))
|
|
620
|
+
)
|
|
621
|
+
: await this.resourceManager.load(downloadProgress.bind(this));
|
|
594
622
|
// 如果在load过程中被销毁了,直接返回,避免继续初始化
|
|
595
623
|
if(this.destroyed) {
|
|
624
|
+
initSpan?.end();
|
|
596
625
|
(window as any).avatarSDKLogger.warn(this.TAG, "sdk destroyed during load, aborting after resourceManager load");
|
|
597
626
|
return
|
|
598
627
|
};
|
|
599
628
|
if(!sessionInfo?.socket_io_url || !sessionInfo?.token) {
|
|
629
|
+
initSpan?.end();
|
|
600
630
|
(window as any).avatarSDKLogger.error(this.TAG, "init error, socket_io_url or token is empty reload");
|
|
601
631
|
return
|
|
602
632
|
};
|
|
@@ -613,6 +643,7 @@ export default class XmovAvatar {
|
|
|
613
643
|
|
|
614
644
|
window.addEventListener('beforeunload', this.handleBeforeUnload);
|
|
615
645
|
document.addEventListener('visibilitychange', this.boundVisibilityChange);
|
|
646
|
+
initSpan?.end();
|
|
616
647
|
return sessionInfo;
|
|
617
648
|
}
|
|
618
649
|
|
|
@@ -1140,6 +1171,9 @@ export default class XmovAvatar {
|
|
|
1140
1171
|
timestamp: Date.now(),
|
|
1141
1172
|
});
|
|
1142
1173
|
this.isfirstReceive = false;
|
|
1174
|
+
// 结束 sdk.first_frame span(首帧数据到达)
|
|
1175
|
+
this.firstFrameSpan?.end();
|
|
1176
|
+
this.firstFrameSpan = null;
|
|
1143
1177
|
}
|
|
1144
1178
|
// 处理下行数据
|
|
1145
1179
|
this.renderScheduler.handleData(type, data);
|
|
@@ -1374,6 +1408,14 @@ export default class XmovAvatar {
|
|
|
1374
1408
|
window.removeEventListener("beforeunload", this.handleBeforeUnload);
|
|
1375
1409
|
document.removeEventListener("visibilitychange", this.boundVisibilityChange);
|
|
1376
1410
|
|
|
1411
|
+
// 清理 Sentry 客户端
|
|
1412
|
+
if (ENABLE_SENTRY && this.sentryReporter) {
|
|
1413
|
+
try {
|
|
1414
|
+
this.sentryReporter.destroy();
|
|
1415
|
+
} catch {}
|
|
1416
|
+
this.sentryReporter = null;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1377
1419
|
(window as any).avatarSDKLogger.log(this.TAG, "SDK 已销毁");
|
|
1378
1420
|
}
|
|
1379
1421
|
|
|
@@ -1432,6 +1474,16 @@ export default class XmovAvatar {
|
|
|
1432
1474
|
|
|
1433
1475
|
speak(ssml: string, is_start: boolean = true, is_end: boolean = true, extra = {client_speak_id: '', client_frame: 0}): string | undefined{
|
|
1434
1476
|
if (this._isClientMode) { this._apiForwarder?.speak(ssml, is_start, is_end, extra); return; }
|
|
1477
|
+
if(this.sentryReporter) {
|
|
1478
|
+
return this.sentryReporter.startSpan('sdk.speak', {
|
|
1479
|
+
op: 'speak',
|
|
1480
|
+
data: { client_speak_id: extra.client_speak_id },
|
|
1481
|
+
}, () => this.speakInternal(ssml, is_start, is_end, extra));
|
|
1482
|
+
}
|
|
1483
|
+
return this.speakInternal(ssml, is_start, is_end, extra);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
private speakInternal(ssml: string, is_start: boolean = true, is_end: boolean = true, extra = {client_speak_id: '', client_frame: 0}): string | undefined{
|
|
1435
1487
|
if(this.enableClientInterrupt && is_start) {
|
|
1436
1488
|
this.renderScheduler.interrupt("speak");
|
|
1437
1489
|
}
|
|
@@ -1578,6 +1630,15 @@ export default class XmovAvatar {
|
|
|
1578
1630
|
if(params.code !== EErrorCode.RENDER_BODY_ERROR && params.code !== EErrorCode.RENDER_FACE_ERROR && params.code !== EErrorCode.BODY_DATA_EXPIRED) {
|
|
1579
1631
|
this.options.onMessage(e)
|
|
1580
1632
|
}
|
|
1633
|
+
// Sentry 错误上报(仅公有云构建时生效)
|
|
1634
|
+
if (ENABLE_SENTRY && this.sentryReporter) {
|
|
1635
|
+
try {
|
|
1636
|
+
this.sentryReporter.captureError(e);
|
|
1637
|
+
} catch (err) {
|
|
1638
|
+
(window as any).avatarSDKLogger.warn(this.TAG, 'Sentry 上报失败', err);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1581
1642
|
this.ttsa?.send("client_error", params);
|
|
1582
1643
|
this.debugOverlay?.addError(e)
|
|
1583
1644
|
}
|
|
@@ -1691,7 +1752,13 @@ export default class XmovAvatar {
|
|
|
1691
1752
|
return;
|
|
1692
1753
|
}
|
|
1693
1754
|
try {
|
|
1694
|
-
|
|
1755
|
+
// sdk.reconnect:单次重连耗时,带轮次/次数 tag
|
|
1756
|
+
const result = this.sentryReporter
|
|
1757
|
+
? await this.sentryReporter.startSpan('sdk.reconnect', {
|
|
1758
|
+
op: 'reconnect',
|
|
1759
|
+
data: { round: this.retryRound, count: this.retryCount },
|
|
1760
|
+
}, () => this._reload())
|
|
1761
|
+
: await this._reload();
|
|
1695
1762
|
if (!this.isRetrying || this.destroyed) {
|
|
1696
1763
|
return;
|
|
1697
1764
|
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BrowserClient,
|
|
3
|
+
defaultStackParser,
|
|
4
|
+
getDefaultIntegrations,
|
|
5
|
+
makeFetchTransport,
|
|
6
|
+
Scope,
|
|
7
|
+
browserTracingIntegration,
|
|
8
|
+
startSpan,
|
|
9
|
+
startInactiveSpan,
|
|
10
|
+
} from '@sentry/browser';
|
|
11
|
+
|
|
12
|
+
import type { SDKError } from 'types/error';
|
|
13
|
+
|
|
14
|
+
/** 渲染噪音 / 数据过期等高频低危错误码,直接过滤不上报 */
|
|
15
|
+
const NOISE_ERROR_CODES = ['3205', '3206', '3305', '30005'];
|
|
16
|
+
|
|
17
|
+
export interface SentryReporterOptions {
|
|
18
|
+
/** Traces 采样率,默认 1.0(100% 全量) */
|
|
19
|
+
tracesSampleRate?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** span 附加属性(兼容 Sentry SpanAttributeValue 的标量子集) */
|
|
23
|
+
type SpanData = Record<string, string | number | boolean | undefined>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Sentry 错误上报器(隔离实例)
|
|
27
|
+
*
|
|
28
|
+
* 使用独立的 BrowserClient + Scope,不污染宿主应用的全局 Sentry 状态。
|
|
29
|
+
* 此文件仅在公有云构建(ENABLE_SENTRY=true)时被打包,
|
|
30
|
+
* 私有化构建时通过编译时常量 + 死代码消除完全移除。
|
|
31
|
+
*/
|
|
32
|
+
export class SentryReporter {
|
|
33
|
+
private client: BrowserClient;
|
|
34
|
+
private scope: Scope;
|
|
35
|
+
private tracesSampleRate: number;
|
|
36
|
+
|
|
37
|
+
constructor(dsn: string, options: SentryReporterOptions = {}) {
|
|
38
|
+
this.tracesSampleRate = options.tracesSampleRate ?? 1.0;
|
|
39
|
+
|
|
40
|
+
// 过滤掉依赖全局状态的集成,避免与宿主应用的 Sentry 实例冲突
|
|
41
|
+
const integrations = getDefaultIntegrations({}).filter(
|
|
42
|
+
(i) =>
|
|
43
|
+
![
|
|
44
|
+
'BrowserApiErrors',
|
|
45
|
+
'BrowserSession',
|
|
46
|
+
'Breadcrumbs',
|
|
47
|
+
'GlobalHandlers',
|
|
48
|
+
'FunctionToString',
|
|
49
|
+
'Dedupe',
|
|
50
|
+
].includes(i.name),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// 显式加入 BrowserTracing(getDefaultIntegrations 未包含),
|
|
54
|
+
// 关闭页面加载/导航/请求自动追踪,只保留手动 startSpan 埋点,避免噪音 span
|
|
55
|
+
integrations.push(
|
|
56
|
+
browserTracingIntegration({
|
|
57
|
+
instrumentPageLoad: false,
|
|
58
|
+
instrumentNavigation: false,
|
|
59
|
+
traceFetch: false,
|
|
60
|
+
traceXHR: false,
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
this.client = new BrowserClient({
|
|
65
|
+
dsn,
|
|
66
|
+
transport: makeFetchTransport,
|
|
67
|
+
stackParser: defaultStackParser,
|
|
68
|
+
integrations,
|
|
69
|
+
release: `xmov-avatar@${VERSION}`,
|
|
70
|
+
tracesSampleRate: this.tracesSampleRate,
|
|
71
|
+
beforeSend(event) {
|
|
72
|
+
// ① 白名单:仅上报 SDK 自身错误(source 标签由 scope 打标),过滤宿主页面异常
|
|
73
|
+
if (event.tags?.source !== 'xmov-avatar-sdk') {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
// ② 噪音码过滤:高频渲染噪音 / 数据过期类,避免事件量过大
|
|
77
|
+
const errorCode = String(event.tags?.error_code ?? '');
|
|
78
|
+
if (NOISE_ERROR_CODES.includes(errorCode)) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
// ③ 指纹去重:按「错误码 + 消息前缀」合并,同码同消息收敛为一个 Issue
|
|
82
|
+
event.fingerprint = [
|
|
83
|
+
errorCode || 'unknown',
|
|
84
|
+
String(event.message ?? '').slice(0, 64),
|
|
85
|
+
];
|
|
86
|
+
// ④ 上下文补全:浏览器/UA(app_id / session_id 等已在 scope 打标)
|
|
87
|
+
event.contexts = {
|
|
88
|
+
...(event.contexts ?? {}),
|
|
89
|
+
browser: { name: navigator.userAgent.slice(0, 200) },
|
|
90
|
+
};
|
|
91
|
+
return event;
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
this.scope = new Scope();
|
|
96
|
+
this.scope.setClient(this.client);
|
|
97
|
+
this.scope.setTag('sdk_version', VERSION);
|
|
98
|
+
this.scope.setTag('source', 'xmov-avatar-sdk');
|
|
99
|
+
this.client.init();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 上报 SDK 错误
|
|
104
|
+
*/
|
|
105
|
+
captureError(error: SDKError): void {
|
|
106
|
+
// 在隔离 scope 上设置本次错误的标签和上下文
|
|
107
|
+
this.scope.setTag('error_code', String(error.error_code));
|
|
108
|
+
this.scope.setTag('error_name', error.error_name);
|
|
109
|
+
this.scope.setTag('session_id', error.session_id);
|
|
110
|
+
this.scope.setExtra('code', error.code);
|
|
111
|
+
this.scope.setExtra('client_speak_id', error.client_speak_id);
|
|
112
|
+
this.scope.setExtra('timestamp', error.timestamp);
|
|
113
|
+
if (error.originalError) {
|
|
114
|
+
this.scope.setExtra('originalError', error.originalError);
|
|
115
|
+
}
|
|
116
|
+
this.scope.captureException(new Error(error.message));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 在隔离 scope 上启动一个 span 并执行回调,回调返回时自动结束。
|
|
121
|
+
* 异常时 span 自动标记 error(由 startSpan 内部处理),span 数据发往本实例的 DSN。
|
|
122
|
+
*/
|
|
123
|
+
startSpan<T>(name: string, ctx: { op?: string; data?: SpanData }, fn: () => T): T {
|
|
124
|
+
return startSpan(
|
|
125
|
+
{
|
|
126
|
+
name,
|
|
127
|
+
op: ctx.op,
|
|
128
|
+
scope: this.scope,
|
|
129
|
+
attributes: ctx.data,
|
|
130
|
+
},
|
|
131
|
+
() => fn(),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 启动一个手动管理生命周期的 span(跨回调/异步时用),
|
|
137
|
+
* 返回的句柄在操作完成时调用 end() 结束 span。
|
|
138
|
+
*/
|
|
139
|
+
startInactiveSpan(name: string, ctx: { op?: string; data?: SpanData }): { end(): void } {
|
|
140
|
+
const span = startInactiveSpan({
|
|
141
|
+
name,
|
|
142
|
+
op: ctx.op,
|
|
143
|
+
scope: this.scope,
|
|
144
|
+
attributes: ctx.data,
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
end: () => span?.end(),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 设置全局标签
|
|
153
|
+
*/
|
|
154
|
+
setTag(key: string, value: string): void {
|
|
155
|
+
this.scope.setTag(key, value);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 设置用户信息
|
|
160
|
+
*/
|
|
161
|
+
setUser(user: { id?: string; ip_address?: string }): void {
|
|
162
|
+
this.scope.setUser(user);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 销毁 Sentry 客户端,flush 未发送的事件
|
|
167
|
+
*/
|
|
168
|
+
async destroy(): Promise<void> {
|
|
169
|
+
await this.client.close(2000);
|
|
170
|
+
}
|
|
171
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -111,6 +111,19 @@ export interface IAvatarOptions {
|
|
|
111
111
|
*/
|
|
112
112
|
onAudioPlaybackData?(data: IAudioPlaybackData): void;
|
|
113
113
|
/**
|
|
114
|
+
* Sentry 错误监控配置
|
|
115
|
+
* 仅在公有云构建(ENABLE_SENTRY=true)时生效,私有化构建会忽略此配置
|
|
116
|
+
* 设为 { enabled: false } 可禁用 Sentry 上报
|
|
117
|
+
*/
|
|
118
|
+
sentry?: {
|
|
119
|
+
enabled?: boolean
|
|
120
|
+
/**
|
|
121
|
+
* 性能 Traces 采样率(0-1),默认 1.0(100% 全量)
|
|
122
|
+
* 容量受限时可下调,如 0.1(10%)
|
|
123
|
+
*/
|
|
124
|
+
tracesSampleRate?: number
|
|
125
|
+
}
|
|
126
|
+
/*
|
|
114
127
|
* 每帧数据回调
|
|
115
128
|
* 返回当前帧的 face + body 原始数据(帧号、body_id 等)
|
|
116
129
|
*/
|