@xmov/avatar 2.0.0-alpha.64 → 2.0.0-alpha.65

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.
@@ -99,6 +99,8 @@ export default class ResourceManager {
99
99
  options: TOptions;
100
100
  sdk: XmovAvatar;
101
101
  session_id?: string;
102
+ /** 单次 init 生命周期内的稳定 request_id,由 generateRequestId 生成;session_id 未返回时也能用于 stopSession 兜底销毁 */
103
+ session_request_id?: string;
102
104
  mouthShapeLib: any;
103
105
  offlineIdle: IOfflineIdle[];
104
106
  offlineCache: Map<string, {
@@ -1,3 +1,10 @@
1
+ /**
2
+ * 生成 request_id:用于 startSession / stopSession 的幂等与兜底销毁。
3
+ * 组成:md5(机器指纹 + 毫秒时间戳 + appId)
4
+ * 同一次 init → destroy 生命周期内始终不变,即使 session_id 尚未返回
5
+ * 也能用 request_id 让服务端销毁已占用的房间资源。
6
+ */
7
+ export declare function generateRequestId(appId: string): string;
1
8
  export interface Headers {
2
9
  [key: string]: string;
3
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmov/avatar",
3
- "version": "2.0.0-alpha.64",
3
+ "version": "2.0.0-alpha.65",
4
4
  "description": "魔珐数字人 JS-SDK",
5
5
  "type": "module",
6
6
  "source": "src/index.ts",
package/src/index.ts CHANGED
@@ -1629,13 +1629,13 @@ export default class XmovAvatar {
1629
1629
  const e = Errors(params)
1630
1630
  if(params.code !== EErrorCode.RENDER_BODY_ERROR && params.code !== EErrorCode.RENDER_FACE_ERROR && params.code !== EErrorCode.BODY_DATA_EXPIRED) {
1631
1631
  this.options.onMessage(e)
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);
1632
+ // Sentry 错误上报(仅公有云构建时生效)
1633
+ if (ENABLE_SENTRY && this.sentryReporter) {
1634
+ try {
1635
+ this.sentryReporter.captureError(e);
1636
+ } catch (err) {
1637
+ (window as any).avatarSDKLogger.warn(this.TAG, 'Sentry 上报失败', err);
1638
+ }
1639
1639
  }
1640
1640
  }
1641
1641
 
@@ -6,7 +6,7 @@ import {
6
6
  } from "../utils/DataInterface";
7
7
  import XmovAvatar from "../index";
8
8
  import { EErrorCode } from "../types/error";
9
- import { headersNeedSign } from "../utils/encodeToken";
9
+ import { headersNeedSign, generateRequestId } from "../utils/encodeToken";
10
10
  // import { isDEV } from "../utils";
11
11
  import CacheManager from './cache-manager';
12
12
 
@@ -121,6 +121,8 @@ export default class ResourceManager {
121
121
  options: TOptions;
122
122
  sdk: XmovAvatar;
123
123
  session_id?: string;
124
+ /** 单次 init 生命周期内的稳定 request_id,由 generateRequestId 生成;session_id 未返回时也能用于 stopSession 兜底销毁 */
125
+ session_request_id?: string;
124
126
  mouthShapeLib: any;
125
127
  offlineIdle: IOfflineIdle[] = []
126
128
  offlineCache = new Map<string, {
@@ -304,6 +306,7 @@ export default class ResourceManager {
304
306
  onDownloadProgress?.(this.progress);
305
307
  }, 100);
306
308
  // http 请求算 10 进度
309
+ this.session_request_id = generateRequestId(this.options.appId);
307
310
  const result = await this.startSession();
308
311
  const res = result as unknown as ISessionResponse;
309
312
  // 先记录 session_id,确保后续 stopSession 能正确释放房间
@@ -365,6 +368,7 @@ export default class ResourceManager {
365
368
 
366
369
 
367
370
  const data = {
371
+ request_id: this.session_request_id,
368
372
  tag: this.options.tag,
369
373
  gateway_type: this.options.gateway_type,
370
374
  ...(this.options.asr_id !== undefined ? { asr_id: this.options.asr_id } : {}),
@@ -404,12 +408,19 @@ export default class ResourceManager {
404
408
  async stopSession(stop_reason: string) {
405
409
  try {
406
410
  const url = `${this.options.gatewayServer}`;
407
- if (!this.session_id) {
411
+ // session_id 尚未返回时(如 init 未完成就 destroy),只要有 request_id 仍发请求,
412
+ // 让服务端用 request_id 匹配并销毁房间占用,避免 init 失败后的资源泄漏。
413
+ if (!this.session_id && !this.session_request_id) {
408
414
  return;
409
415
  }
410
- const data = {
411
- session_id: this.session_id,
416
+ const data: Record<string, any> = {
412
417
  stop_reason,
418
+ };
419
+ if (this.session_id) {
420
+ data.session_id = this.session_id;
421
+ }
422
+ if (this.session_request_id) {
423
+ data.request_id = this.session_request_id;
413
424
  }
414
425
  const { headers, data: signedData } = headersNeedSign(this.options.appId, this.options.appSecret, "DELETE", url, data);
415
426
  const response = await request(url, { method: "DELETE", data: signedData, headers: { ...headers, ...this.options.headers } });
@@ -908,5 +919,7 @@ export default class ResourceManager {
908
919
  this.bg = null;
909
920
  }
910
921
  this.progress = 0
922
+ this.session_id = undefined;
923
+ this.session_request_id = undefined;
911
924
  }
912
925
  }
@@ -1,9 +1,24 @@
1
1
  import md5 from 'blueimp-md5';
2
+ import { generateFingerprint } from './index';
2
3
 
3
4
  function encodeWithMd5(s: string): string {
4
5
  return md5(s);
5
6
  }
6
7
 
8
+ /**
9
+ * 生成 request_id:用于 startSession / stopSession 的幂等与兜底销毁。
10
+ * 组成:md5(机器指纹 + 毫秒时间戳 + appId)
11
+ * 同一次 init → destroy 生命周期内始终不变,即使 session_id 尚未返回
12
+ * 也能用 request_id 让服务端销毁已占用的房间资源。
13
+ */
14
+ export function generateRequestId(appId: string): string {
15
+ const fingerprint = (() => {
16
+ try { return generateFingerprint(); } catch { return 'unknown'; }
17
+ })();
18
+ const payload = `${fingerprint}|${Date.now()}|${appId}`;
19
+ return md5(payload);
20
+ }
21
+
7
22
  function escapeNonAsciiForPythonJson(value: string): string {
8
23
  return value.replace(/[^\u0000-\u007E]/g, character => {
9
24
  return `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`;