@xmov/avatar 2.4.0-beta.3 → 2.4.0-beta.5

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.
@@ -4,6 +4,7 @@ interface RequestOptions {
4
4
  data?: Record<string, any>;
5
5
  headers?: Record<string, string>;
6
6
  keepalive?: boolean;
7
+ signal?: AbortSignal;
7
8
  }
8
9
  export default function request(url: string, options?: RequestOptions): Promise<Response>;
9
10
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmov/avatar",
3
- "version": "2.4.0-beta.3",
3
+ "version": "2.4.0-beta.5",
4
4
  "description": "魔珐数字人 JS-SDK",
5
5
  "type": "module",
6
6
  "source": "src/index.ts",
package/src/index.ts CHANGED
@@ -643,6 +643,9 @@ export default class XmovAvatar {
643
643
  this.pendingInvisibleMode = true;
644
644
  }
645
645
 
646
+ // Session 请求和资源下载期间也需要处理页面刷新。
647
+ window.addEventListener('beforeunload', this.handleBeforeUnload);
648
+
646
649
  // 加载资源(sdk.resource_load:资源包下载/解析耗时)
647
650
  (window as any).performanceTracker.markStart(performanceConstant.load_resource);
648
651
  (window as any).performanceTracker.markStart(performanceConstant.first_avatar_render);
@@ -675,7 +678,6 @@ export default class XmovAvatar {
675
678
  // 设置上报使用ttsa
676
679
  (window as any).performanceTracker.setReportFunc(this.ttsa);
677
680
 
678
- window.addEventListener('beforeunload', this.handleBeforeUnload);
679
681
  document.addEventListener('visibilitychange', this.boundVisibilityChange);
680
682
  initSpan?.end();
681
683
  return sessionInfo;
@@ -1318,7 +1320,9 @@ export default class XmovAvatar {
1318
1320
  private handleBeforeUnload = () => {
1319
1321
  if (!this._isClientMode && (this.resourceManager?.session_id || this.resourceManager?.session_request_id)) {
1320
1322
  // Page dismissal cannot wait for E2E and TTSA shutdown before starting DELETE.
1321
- this.beforeUnloadStopSessionPromise ??= this.resourceManager.stopSession("beforeunload", { keepalive: true });
1323
+ this.beforeUnloadStopSessionPromise ??= this.resourceManager.stopSession("beforeunload", { keepalive: true }).catch((error) => {
1324
+ (window as any).avatarSDKLogger.warn(this.TAG, "页面卸载时停止会话失败", error);
1325
+ });
1322
1326
  }
1323
1327
  void this.destroy("beforeunload");
1324
1328
  };
@@ -1464,9 +1468,10 @@ export default class XmovAvatar {
1464
1468
  this.ttsa?.sendSdkPoint('close_session', {
1465
1469
  reason: stop_reason,
1466
1470
  });
1467
- const res = await (this.beforeUnloadStopSessionPromise ?? this.resourceManager.stopSession(stop_reason));
1471
+ // An unload DELETE is already in flight; neither await nor cancel it.
1472
+ const res = this.beforeUnloadStopSessionPromise ? undefined : await this.resourceManager.stopSession(stop_reason);
1468
1473
  await this.destroyClient();
1469
- if(res) {
1474
+ if(res || this.beforeUnloadStopSessionPromise) {
1470
1475
  this.onStatusChange(AvatarStatus.close);
1471
1476
  }
1472
1477
  return res
@@ -1487,8 +1492,8 @@ export default class XmovAvatar {
1487
1492
  (window as any).avatarSDKLogger.warn(this.TAG, "TTSA client_quit 未确认,继续释放本地资源", error);
1488
1493
  } finally {
1489
1494
  try {
1490
- if (!this._isClientMode) {
1491
- await (this.beforeUnloadStopSessionPromise ?? this.resourceManager.stopSession(stopReason));
1495
+ if (!this._isClientMode && !this.beforeUnloadStopSessionPromise) {
1496
+ await this.resourceManager.stopSession(stopReason);
1492
1497
  }
1493
1498
  } finally {
1494
1499
  await this.destroyClient();
@@ -10,6 +10,8 @@ import { headersNeedSign, generateRequestId } from "../utils/encodeToken";
10
10
  // import { isDEV } from "../utils";
11
11
  import CacheManager from './cache-manager';
12
12
 
13
+ const STOP_SESSION_TIMEOUT_MS = 3000;
14
+
13
15
  /**
14
16
  * 资源处理模块,加载并存储资源
15
17
  */
@@ -407,6 +409,7 @@ export default class ResourceManager {
407
409
  }
408
410
 
409
411
  async stopSession(stop_reason: string, options: { keepalive?: boolean } = {}) {
412
+ let timeout: ReturnType<typeof setTimeout> | undefined;
410
413
  try {
411
414
  const url = `${this.options.gatewayServer}`;
412
415
  // session_id 尚未返回时(如 init 未完成就 destroy),只要有 request_id 仍发请求,
@@ -424,7 +427,19 @@ export default class ResourceManager {
424
427
  data.request_id = this.session_request_id;
425
428
  }
426
429
  const { headers, data: signedData } = headersNeedSign(this.options.appId, this.options.appSecret, "DELETE", url, data);
427
- const response = await request(url, { method: "DELETE", data: signedData, headers: { ...headers, ...this.options.headers }, keepalive: options.keepalive });
430
+ // Unload requests must be allowed to outlive the document. Ordinary
431
+ // DELETEs have a deadline covering both response headers and body.
432
+ const controller = options.keepalive ? undefined : new AbortController();
433
+ if (controller) {
434
+ timeout = setTimeout(() => controller.abort(), STOP_SESSION_TIMEOUT_MS);
435
+ }
436
+ const response = await request(url, {
437
+ method: "DELETE",
438
+ data: signedData,
439
+ headers: { ...headers, ...this.options.headers },
440
+ keepalive: options.keepalive,
441
+ signal: controller?.signal,
442
+ });
428
443
  return await response.json();
429
444
  } catch (error) {
430
445
  this.sdk.onMessage({
@@ -432,6 +447,8 @@ export default class ResourceManager {
432
447
  message: `Error: 停止会话失败`,
433
448
  e: error,
434
449
  });
450
+ } finally {
451
+ if (timeout !== undefined) clearTimeout(timeout);
435
452
  }
436
453
  }
437
454
 
@@ -5,6 +5,7 @@ interface RequestOptions {
5
5
  data?: Record<string, any>
6
6
  headers?: Record<string, string>
7
7
  keepalive?: boolean
8
+ signal?: AbortSignal
8
9
  }
9
10
 
10
11
  interface ErrorResponse {
@@ -23,6 +24,7 @@ export default async function request(
23
24
  const config: RequestInit = {
24
25
  method,
25
26
  keepalive: options.keepalive,
27
+ signal: options.signal,
26
28
  // headers,
27
29
  headers: {
28
30
  'Content-Type': 'application/json',