customer-chat-sdk 1.1.9 → 1.1.11

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.
@@ -24,6 +24,16 @@ export declare class IconManager {
24
24
  private lastTouchPosition;
25
25
  private touchStartTime;
26
26
  private clickThreshold;
27
+ private cachedContainer;
28
+ private cachedContainerRect;
29
+ private cachedIconSize;
30
+ private lastContainerUpdateTime;
31
+ private containerUpdateInterval;
32
+ private cachedIframes;
33
+ private lastIframeUpdateTime;
34
+ private iframeUpdateInterval;
35
+ private rafId;
36
+ private pendingPosition;
27
37
  private onDragHandler;
28
38
  private stopDragHandler;
29
39
  private startDragHandler;
@@ -1 +1 @@
1
- {"version":3,"file":"IconManager.d.ts","sourceRoot":"","sources":["../../src/core/IconManager.ts"],"names":[],"mappings":"AAGA,UAAU,mBAAmB;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,UAAU,YAAY;IACpB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CACpB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,oBAAoB,CAAqC;IACjE,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,MAAM,CAAoC;IAGlD,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,cAAc,CAAK;IAG3B,OAAO,CAAC,aAAa,CAAsD;IAC3E,OAAO,CAAC,eAAe,CAAsD;IAC7E,OAAO,CAAC,gBAAgB,CAAsD;IAC9E,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,mBAAmB,CAA2C;IACtE,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,aAAa,CAAsB;gBAE/B,QAAQ,CAAC,EAAE,YAAY,EAAE,KAAK,GAAE,OAAe,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM;IAQ1F;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwH3B;;OAEG;IACH,sBAAsB,IAAI,IAAI;IAI9B;;OAEG;IACH,IAAI,IAAI,IAAI;IAoCZ;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI;IAIhC;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAqB7C;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI;IAI1B;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAInC;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAInD;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI;IA8BpD;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAUzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAavB;;OAEG;IACH,OAAO,CAAC,SAAS;IAgHjB;;OAEG;IACH,OAAO,CAAC,MAAM;IAqHd;;OAEG;IACH,OAAO,CAAC,QAAQ;IAkEhB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAgCzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAUnB;;OAEG;IACH,YAAY,IAAI,IAAI;IAmBpB;;OAEG;IACH,WAAW,IAAI,IAAI;IAmBnB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAqCxB;;OAEG;IACH,OAAO,CAAC,WAAW;CAmDpB"}
1
+ {"version":3,"file":"IconManager.d.ts","sourceRoot":"","sources":["../../src/core/IconManager.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,7 @@ export declare class IframeManager {
19
19
  private isCreated;
20
20
  private debug;
21
21
  private targetElement;
22
+ private messageHandler;
22
23
  constructor(config?: IframeOptions);
23
24
  /**
24
25
  * 初始化iframe(隐藏状态)
@@ -1 +1 @@
1
- {"version":3,"file":"IframeManager.d.ts","sourceRoot":"","sources":["../../src/core/IframeManager.ts"],"names":[],"mappings":"AACA,UAAU,aAAa;IACrB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,OAAO,CAAA;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC7B,SAAS,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAA;IACpD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,aAAa,CAA2B;gBAEpC,MAAM,GAAE,aAAkB;IActC;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB3B;;OAEG;IACH,IAAI,IAAI,IAAI;IA2CZ;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,IAAI,IAAI,IAAI;IA6BZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,OAAO,IAAI,IAAI;IAgBf;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI;IAM7B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAa/B;;OAEG;IACH,OAAO,CAAC,YAAY;IAuLpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA8D1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoCzB;;OAEG;IACH,OAAO,CAAC,cAAc;IAKtB;;;OAGG;IACH,OAAO,CAAC,aAAa;IAOrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAS5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAwD3B;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CA0BzB"}
1
+ {"version":3,"file":"IframeManager.d.ts","sourceRoot":"","sources":["../../src/core/IframeManager.ts"],"names":[],"mappings":"AACA,UAAU,aAAa;IACrB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,OAAO,CAAA;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC7B,SAAS,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAA;IACpD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,aAAa,CAA2B;IAChD,OAAO,CAAC,cAAc,CAA+C;gBAEzD,MAAM,GAAE,aAAkB;IAetC;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC3B;;OAEG;IACH,IAAI,IAAI,IAAI;IA2CZ;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,IAAI,IAAI,IAAI;IA6BZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,OAAO,IAAI,IAAI;IAsBf;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI;IAM7B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAa/B;;OAEG;IACH,OAAO,CAAC,YAAY;IAuLpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA8D1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoCzB;;OAEG;IACH,OAAO,CAAC,cAAc;IAKtB;;;OAGG;IACH,OAAO,CAAC,aAAa;IAOrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAiB5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAwD3B;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CA0BzB"}
@@ -22,6 +22,19 @@ class IconManager {
22
22
  this.lastTouchPosition = { x: 0, y: 0 }; // 最后触摸位置
23
23
  this.touchStartTime = 0; // 触摸开始时间
24
24
  this.clickThreshold = 15; // 点击阈值(像素)
25
+ // 性能优化:缓存容器信息,避免频繁查询 DOM
26
+ this.cachedContainer = null;
27
+ this.cachedContainerRect = null;
28
+ this.cachedIconSize = { width: 0, height: 0 };
29
+ this.lastContainerUpdateTime = 0;
30
+ this.containerUpdateInterval = 100; // 每 100ms 更新一次容器信息(避免频繁重排)
31
+ // 性能优化:缓存所有 iframe 的位置信息(用于检测鼠标是否在 iframe 上)
32
+ this.cachedIframes = [];
33
+ this.lastIframeUpdateTime = 0;
34
+ this.iframeUpdateInterval = 50; // 每 50ms 更新一次 iframe 位置信息(更频繁,因为 iframe 可能移动)
35
+ // 性能优化:使用 requestAnimationFrame 节流位置更新
36
+ this.rafId = null;
37
+ this.pendingPosition = { x: 0, y: 0, needsUpdate: false };
25
38
  // 事件处理器引用(用于清理)
26
39
  this.onDragHandler = null;
27
40
  this.stopDragHandler = null;
@@ -321,6 +334,14 @@ class IconManager {
321
334
  this.dragOffset.x = clientX - iconRect.left;
322
335
  this.dragOffset.y = clientY - iconRect.top;
323
336
  // 注意:不在这里转换位置,只在真正开始拖动时才转换(在 onDrag 中)
337
+ // 性能优化:在拖动开始时预加载所有 iframe 位置信息
338
+ // 这样可以避免在拖动过程中频繁查询 DOM
339
+ const allIframes = document.querySelectorAll('iframe');
340
+ this.cachedIframes = Array.from(allIframes).map(iframe => ({
341
+ element: iframe,
342
+ rect: iframe.getBoundingClientRect()
343
+ }));
344
+ this.lastIframeUpdateTime = Date.now();
324
345
  // 添加 document 事件监听器
325
346
  if (this.onDragHandler) {
326
347
  document.addEventListener('mousemove', this.onDragHandler);
@@ -363,7 +384,7 @@ class IconManager {
363
384
  };
364
385
  window.addEventListener('blur', this.blurHandler);
365
386
  // 3. 添加超时机制(如果一段时间没有收到 mousemove 事件,自动停止拖动)
366
- // 这可以处理鼠标移动到 iframe 上的情况
387
+ // 优化:缩短超时时间到 100ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
367
388
  this.dragTimeoutId = window.setTimeout(() => {
368
389
  if (this.isDragging) {
369
390
  if (this.debug) {
@@ -371,7 +392,7 @@ class IconManager {
371
392
  }
372
393
  this.stopDrag();
373
394
  }
374
- }, 500); // 500ms 没有移动事件,自动停止拖动(处理鼠标移动到 iframe 上的情况)
395
+ }, 100); // 缩短到 100ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
375
396
  if (this.debug) {
376
397
  console.log('Drag start');
377
398
  }
@@ -389,7 +410,41 @@ class IconManager {
389
410
  if (!this.iconElement)
390
411
  return;
391
412
  e.preventDefault();
413
+ const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
414
+ const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
415
+ // 检测鼠标是否在任何 iframe 上(通过坐标判断)
416
+ // 如果检测到鼠标在 iframe 区域,立即停止拖动
417
+ // 重要:需要检测所有 iframe(包括嵌套的),因为任何 iframe 都会导致事件丢失
418
+ if (this.isDragging) {
419
+ const now = Date.now();
420
+ // 性能优化:缓存 iframe 位置信息,避免频繁查询 DOM
421
+ if (this.cachedIframes.length === 0 ||
422
+ now - this.lastIframeUpdateTime > this.iframeUpdateInterval) {
423
+ // 更新 iframe 缓存
424
+ const allIframes = document.querySelectorAll('iframe');
425
+ this.cachedIframes = Array.from(allIframes).map(iframe => ({
426
+ element: iframe,
427
+ rect: iframe.getBoundingClientRect()
428
+ }));
429
+ this.lastIframeUpdateTime = now;
430
+ }
431
+ // 检查鼠标是否在任何 iframe 上
432
+ for (const { rect } of this.cachedIframes) {
433
+ if (clientX >= rect.left &&
434
+ clientX <= rect.right &&
435
+ clientY >= rect.top &&
436
+ clientY <= rect.bottom) {
437
+ // 鼠标在 iframe 上,立即停止拖动
438
+ if (this.debug) {
439
+ console.log('Mouse over iframe, stopping drag immediately');
440
+ }
441
+ this.stopDrag();
442
+ return;
443
+ }
444
+ }
445
+ }
392
446
  // 重置超时定时器(每次移动都重置,确保只有真正停止移动时才触发超时)
447
+ // 优化:缩短超时时间到 100ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
393
448
  if (this.dragTimeoutId !== null) {
394
449
  window.clearTimeout(this.dragTimeoutId);
395
450
  this.dragTimeoutId = window.setTimeout(() => {
@@ -399,10 +454,8 @@ class IconManager {
399
454
  }
400
455
  this.stopDrag();
401
456
  }
402
- }, 500); // 500ms 没有移动事件,自动停止拖动
457
+ }, 100); // 缩短到 100ms,更快检测到事件丢失(特别是嵌套 iframe 场景)
403
458
  }
404
- const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
405
- const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
406
459
  // 检查是否有足够的移动距离
407
460
  const deltaX = Math.abs(clientX - this.lastTouchPosition.x);
408
461
  const deltaY = Math.abs(clientY - this.lastTouchPosition.y);
@@ -446,17 +499,31 @@ class IconManager {
446
499
  return;
447
500
  }
448
501
  try {
502
+ // 性能优化:缓存容器信息,避免频繁查询 DOM
503
+ const now = Date.now();
449
504
  const container = this.getTargetElement();
450
505
  if (!container) {
451
506
  return;
452
507
  }
453
- const containerRect = container.getBoundingClientRect();
508
+ // 只在必要时更新容器信息(避免频繁重排)
509
+ if (!this.cachedContainer ||
510
+ this.cachedContainer !== container ||
511
+ now - this.lastContainerUpdateTime > this.containerUpdateInterval) {
512
+ this.cachedContainer = container;
513
+ this.cachedContainerRect = container.getBoundingClientRect();
514
+ this.cachedIconSize = {
515
+ width: this.iconElement.offsetWidth,
516
+ height: this.iconElement.offsetHeight
517
+ };
518
+ this.lastContainerUpdateTime = now;
519
+ }
520
+ const containerRect = this.cachedContainerRect;
521
+ const iconWidth = this.cachedIconSize.width;
522
+ const iconHeight = this.cachedIconSize.height;
454
523
  // 计算新位置
455
524
  let newX = clientX - this.dragOffset.x - containerRect.left;
456
525
  let newY = clientY - this.dragOffset.y - containerRect.top;
457
526
  // 限制在容器内
458
- const iconWidth = this.iconElement.offsetWidth;
459
- const iconHeight = this.iconElement.offsetHeight;
460
527
  if (container === document.body) {
461
528
  // 限制在视口内
462
529
  newX = Math.max(0, Math.min(newX, window.innerWidth - iconWidth));
@@ -469,11 +536,22 @@ class IconManager {
469
536
  newX = Math.max(0, Math.min(newX, containerWidth - iconWidth));
470
537
  newY = Math.max(0, Math.min(newY, containerHeight - iconHeight));
471
538
  }
472
- // 更新位置
473
- this.iconElement.style.left = `${newX}px`;
474
- this.iconElement.style.top = `${newY}px`;
475
- this.iconElement.style.right = 'auto';
476
- this.iconElement.style.bottom = 'auto';
539
+ // 性能优化:使用 requestAnimationFrame 节流位置更新
540
+ this.pendingPosition.x = newX;
541
+ this.pendingPosition.y = newY;
542
+ this.pendingPosition.needsUpdate = true;
543
+ if (this.rafId === null) {
544
+ this.rafId = requestAnimationFrame(() => {
545
+ this.rafId = null;
546
+ if (this.pendingPosition.needsUpdate && this.iconElement && this.isDragging) {
547
+ this.iconElement.style.left = `${this.pendingPosition.x}px`;
548
+ this.iconElement.style.top = `${this.pendingPosition.y}px`;
549
+ this.iconElement.style.right = 'auto';
550
+ this.iconElement.style.bottom = 'auto';
551
+ this.pendingPosition.needsUpdate = false;
552
+ }
553
+ });
554
+ }
477
555
  // 更新最后位置
478
556
  this.lastTouchPosition.x = clientX;
479
557
  this.lastTouchPosition.y = clientY;
@@ -573,6 +651,16 @@ class IconManager {
573
651
  window.clearTimeout(this.dragTimeoutId);
574
652
  this.dragTimeoutId = null;
575
653
  }
654
+ // 清理 requestAnimationFrame
655
+ if (this.rafId !== null) {
656
+ cancelAnimationFrame(this.rafId);
657
+ this.rafId = null;
658
+ }
659
+ // 清理缓存
660
+ this.cachedContainer = null;
661
+ this.cachedContainerRect = null;
662
+ this.cachedIframes = []; // 清理 iframe 缓存
663
+ this.pendingPosition.needsUpdate = false;
576
664
  }
577
665
  /**
578
666
  * 处理点击事件
@@ -724,6 +812,7 @@ class IframeManager {
724
812
  this.isCreated = false;
725
813
  this.debug = false; // debug 模式标志
726
814
  this.targetElement = null; // 目标元素(用于自适应宽度)
815
+ this.messageHandler = null; // 消息监听器引用(用于清理)
727
816
  this.config = {
728
817
  src: '',
729
818
  mode: 'auto', // 默认自动检测设备类型
@@ -733,7 +822,8 @@ class IframeManager {
733
822
  ...config
734
823
  };
735
824
  this.debug = config.debug ?? false;
736
- this.setupMessageListener();
825
+ // 不在构造函数中添加消息监听器,延迟到 init() 中添加
826
+ // 这样可以避免创建多个实例时产生多个监听器
737
827
  }
738
828
  /**
739
829
  * 初始化iframe(隐藏状态)
@@ -741,6 +831,8 @@ class IframeManager {
741
831
  */
742
832
  async init() {
743
833
  try {
834
+ // 设置消息监听器(在 init() 中而不是构造函数中,避免多次创建实例时产生多个监听器)
835
+ this.setupMessageListener();
744
836
  // 关键修复:在初始化前,先清理页面上所有旧的容器元素
745
837
  // 防止切换模式或多次初始化时产生重复的元素
746
838
  this.cleanupOrphanedElements();
@@ -755,6 +847,11 @@ class IframeManager {
755
847
  catch (error) {
756
848
  // 错误始终输出
757
849
  console.error('Failed to initialize iframe:', error);
850
+ // 如果初始化失败,清理消息监听器
851
+ if (this.messageHandler) {
852
+ window.removeEventListener('message', this.messageHandler);
853
+ this.messageHandler = null;
854
+ }
758
855
  throw error;
759
856
  }
760
857
  }
@@ -842,6 +939,11 @@ class IframeManager {
842
939
  */
843
940
  destroy() {
844
941
  this.hide();
942
+ // 移除消息监听器(防止内存泄漏)
943
+ if (this.messageHandler) {
944
+ window.removeEventListener('message', this.messageHandler);
945
+ this.messageHandler = null;
946
+ }
845
947
  // 移除容器
846
948
  if (this.containerElement) {
847
949
  this.containerElement.remove();
@@ -1169,12 +1271,18 @@ class IframeManager {
1169
1271
  * 设置消息监听
1170
1272
  */
1171
1273
  setupMessageListener() {
1172
- window.addEventListener('message', (event) => {
1274
+ // 如果已存在,先移除旧的监听器(防止重复添加)
1275
+ if (this.messageHandler) {
1276
+ window.removeEventListener('message', this.messageHandler);
1277
+ }
1278
+ // 创建新的消息处理器并保存引用
1279
+ this.messageHandler = (event) => {
1173
1280
  // 验证消息来源(可选的安全检查)
1174
1281
  if (!this.config.src || event.origin === new URL(this.config.src).origin) {
1175
1282
  this.handleIframeMessage(event.data);
1176
1283
  }
1177
- }, false);
1284
+ };
1285
+ window.addEventListener('message', this.messageHandler, false);
1178
1286
  }
1179
1287
  /**
1180
1288
  * 处理来自iframe的消息
@@ -20987,26 +21095,49 @@ class CustomerServiceSDK {
20987
21095
  this.screenshotManager = null;
20988
21096
  this.config = null;
20989
21097
  this.isInitialized = false;
21098
+ this.isInitializing = false; // 初始化锁,防止并发初始化
20990
21099
  this.initResult = null; // 保存初始化结果
20991
21100
  this.debug = false; // debug 模式标志
21101
+ this.lastIconConfig = null; // 保存上一次的图标配置
20992
21102
  }
20993
21103
  /**
20994
21104
  * 初始化 SDK
20995
21105
  * @param config SDK配置
20996
21106
  * @param options UI选项(可选)
21107
+ * @param forceReinit 是否强制重新初始化(用于更新token等配置)
20997
21108
  * @returns 返回初始化信息(包含设备ID等)
20998
21109
  */
20999
- async init(config, options) {
21000
- if (this.isInitialized) {
21110
+ async init(config, options, forceReinit = false) {
21111
+ // 防止并发初始化
21112
+ if (this.isInitializing) {
21113
+ throw new Error('SDK is already initializing. Please wait for the current initialization to complete.');
21114
+ }
21115
+ // 如果已经初始化且不强制重新初始化,返回之前保存的初始化信息
21116
+ if (this.isInitialized && !forceReinit) {
21001
21117
  if (this.debug) {
21002
- console.warn('CustomerSDK already initialized');
21118
+ console.warn('CustomerSDK already initialized, returning cached result. Use forceReinit=true to reinitialize.');
21003
21119
  }
21004
- // 如果已经初始化,返回之前保存的初始化信息
21005
21120
  if (this.initResult) {
21006
21121
  return this.initResult;
21007
21122
  }
21008
21123
  throw new Error('SDK already initialized but cannot retrieve initialization info');
21009
21124
  }
21125
+ // 设置初始化锁
21126
+ this.isInitializing = true;
21127
+ // 如果需要强制重新初始化,先清理旧资源
21128
+ if (this.isInitialized && forceReinit) {
21129
+ if (this.debug) {
21130
+ console.log('Force reinitializing SDK...');
21131
+ }
21132
+ // 清理旧的 iframe 和截图管理器
21133
+ this.iframeManager?.destroy();
21134
+ this.screenshotManager?.destroy();
21135
+ this.iframeManager = null;
21136
+ this.screenshotManager = null;
21137
+ // 注意:图标管理器暂时保留,稍后检查配置是否变化再决定是否重新创建
21138
+ // 重置初始化标志
21139
+ this.isInitialized = false;
21140
+ }
21010
21141
  this.config = config;
21011
21142
  this.debug = config.debug ?? false;
21012
21143
  try {
@@ -21025,11 +21156,47 @@ class CustomerServiceSDK {
21025
21156
  agent: config.agent,
21026
21157
  timestamp: Date.now()
21027
21158
  };
21028
- // 创建悬浮图标管理器(支持自定义位置和传送目标)
21029
- const iconPosition = options?.iconPosition || undefined;
21030
- const iconTarget = options?.target || undefined;
21031
- this.iconManager = new IconManager(iconPosition, this.debug, iconTarget);
21032
- await this.iconManager.show();
21159
+ // 处理图标管理器(优化:如果配置没变化,保留图标避免闪烁)
21160
+ // 如果 options 未提供,使用之前保存的配置(避免更新 token 时图标闪烁)
21161
+ const iconPosition = options?.iconPosition !== undefined
21162
+ ? options.iconPosition
21163
+ : (this.lastIconConfig?.position || undefined);
21164
+ const iconTarget = options?.target !== undefined
21165
+ ? options.target
21166
+ : (this.lastIconConfig?.target || undefined);
21167
+ // 检查图标配置是否变化
21168
+ const iconConfigChanged = this.isIconConfigChanged(iconPosition, iconTarget);
21169
+ if (iconConfigChanged) {
21170
+ // 配置变化了,需要重新创建图标管理器
21171
+ if (this.iconManager) {
21172
+ this.iconManager.hide();
21173
+ this.iconManager = null; // 先清空引用,避免引用混乱
21174
+ if (this.debug) {
21175
+ console.log('Icon config changed, recreating icon manager');
21176
+ }
21177
+ }
21178
+ this.iconManager = new IconManager(iconPosition, this.debug, iconTarget);
21179
+ await this.iconManager.show();
21180
+ // 保存新的配置
21181
+ this.lastIconConfig = { position: iconPosition, target: iconTarget };
21182
+ }
21183
+ else {
21184
+ // 配置没变化,保留图标管理器(避免闪烁)
21185
+ if (!this.iconManager) {
21186
+ // 如果不存在,创建新的
21187
+ this.iconManager = new IconManager(iconPosition, this.debug, iconTarget);
21188
+ await this.iconManager.show();
21189
+ }
21190
+ else {
21191
+ // 已存在,确保显示(可能被隐藏了)
21192
+ await this.iconManager.show();
21193
+ if (this.debug) {
21194
+ console.log('Icon config unchanged, keeping existing icon manager');
21195
+ }
21196
+ }
21197
+ // 更新配置记录
21198
+ this.lastIconConfig = { position: iconPosition, target: iconTarget };
21199
+ }
21033
21200
  // 创建iframe管理器(自动检测设备类型)
21034
21201
  // 如果提供了 target,iframe 会自适应 target 的宽度(PC 和移动端都适用)
21035
21202
  // 如果没有 target,PC 模式使用默认宽度,移动端全屏
@@ -21098,8 +21265,33 @@ class CustomerServiceSDK {
21098
21265
  catch (error) {
21099
21266
  // 错误始终输出
21100
21267
  console.error('Failed to initialize CustomerSDK:', error);
21268
+ // 清理已创建的资源(防止资源泄漏)
21269
+ try {
21270
+ if (this.iconManager) {
21271
+ this.iconManager.hide();
21272
+ this.iconManager = null;
21273
+ }
21274
+ if (this.iframeManager) {
21275
+ this.iframeManager.destroy();
21276
+ this.iframeManager = null;
21277
+ }
21278
+ if (this.screenshotManager) {
21279
+ this.screenshotManager.destroy();
21280
+ this.screenshotManager = null;
21281
+ }
21282
+ this.isInitialized = false;
21283
+ this.initResult = null;
21284
+ }
21285
+ catch (cleanupError) {
21286
+ // 清理过程中的错误不应该影响原始错误的抛出
21287
+ console.error('Error during cleanup after initialization failure:', cleanupError);
21288
+ }
21101
21289
  throw error;
21102
21290
  }
21291
+ finally {
21292
+ // 释放初始化锁
21293
+ this.isInitializing = false;
21294
+ }
21103
21295
  }
21104
21296
  /**
21105
21297
  * 显示/隐藏悬浮图标
@@ -21243,18 +21435,44 @@ class CustomerServiceSDK {
21243
21435
  console.log('📸 截图配置已更新:', options);
21244
21436
  }
21245
21437
  }
21438
+ /**
21439
+ * 更新 token(用于用户登录/退出场景)
21440
+ * 如果已初始化,会重新创建 iframe 并更新 URL
21441
+ * @param token 新的 token(传空字符串或 undefined 表示移除 token)
21442
+ * @param options UI选项(可选,用于重新初始化时的配置)
21443
+ * @returns 返回更新后的初始化信息
21444
+ */
21445
+ async updateToken(token, options) {
21446
+ if (!this.isInitialized) {
21447
+ throw new Error('SDK not initialized. Call init() first.');
21448
+ }
21449
+ if (!this.config) {
21450
+ throw new Error('SDK config not found');
21451
+ }
21452
+ // 更新配置中的 token
21453
+ const updatedConfig = {
21454
+ ...this.config,
21455
+ token: token && token.trim() !== '' ? token : undefined
21456
+ };
21457
+ if (this.debug) {
21458
+ console.log('Updating token:', token ? 'Token provided' : 'Token removed');
21459
+ }
21460
+ // 强制重新初始化以应用新的 token
21461
+ return await this.init(updatedConfig, options, true);
21462
+ }
21246
21463
  /**
21247
21464
  * 销毁 SDK
21248
21465
  */
21249
21466
  destroy() {
21250
21467
  this.iconManager?.hide();
21251
- this.iframeManager?.close();
21468
+ this.iframeManager?.destroy(); // 使用 destroy 而不是 close,确保完全清理
21252
21469
  this.screenshotManager?.destroy();
21253
21470
  this.iconManager = null;
21254
21471
  this.iframeManager = null;
21255
21472
  this.screenshotManager = null;
21256
21473
  this.config = null;
21257
21474
  this.initResult = null;
21475
+ this.lastIconConfig = null;
21258
21476
  this.isInitialized = false;
21259
21477
  if (this.debug) {
21260
21478
  console.log('CustomerSDK destroyed');
@@ -21299,6 +21517,36 @@ class CustomerServiceSDK {
21299
21517
  return fallbackId;
21300
21518
  }
21301
21519
  }
21520
+ /**
21521
+ * 检查图标配置是否变化
21522
+ */
21523
+ isIconConfigChanged(newPosition, newTarget) {
21524
+ // 如果没有保存的配置,说明是首次初始化,需要创建
21525
+ if (!this.lastIconConfig) {
21526
+ return true;
21527
+ }
21528
+ // 比较位置配置
21529
+ const positionChanged = JSON.stringify(this.lastIconConfig.position) !== JSON.stringify(newPosition);
21530
+ // 比较 target 配置
21531
+ const oldTarget = this.lastIconConfig.target;
21532
+ let targetChanged = false;
21533
+ if (oldTarget !== newTarget) {
21534
+ // 如果引用不同,进一步检查
21535
+ if (typeof oldTarget === 'string' && typeof newTarget === 'string') {
21536
+ // 都是字符串,比较值
21537
+ targetChanged = oldTarget !== newTarget;
21538
+ }
21539
+ else if (oldTarget instanceof HTMLElement && newTarget instanceof HTMLElement) {
21540
+ // 都是 HTMLElement,比较引用(引用不同就是变化了)
21541
+ targetChanged = true;
21542
+ }
21543
+ else {
21544
+ // 类型不同,肯定是变化了
21545
+ targetChanged = true;
21546
+ }
21547
+ }
21548
+ return positionChanged || targetChanged;
21549
+ }
21302
21550
  /**
21303
21551
  * 构建iframe URL(带用户参数和设备ID)
21304
21552
  */
@@ -21326,13 +21574,14 @@ let globalSDKInstance = null;
21326
21574
  * 初始化 Customer SDK
21327
21575
  * @param config SDK配置
21328
21576
  * @param options UI选项(可选)
21577
+ * @param forceReinit 是否强制重新初始化(用于更新token等配置)
21329
21578
  * @returns 返回初始化信息(包含设备ID等)
21330
21579
  */
21331
- const init = async (config, options) => {
21580
+ const init = async (config, options, forceReinit = false) => {
21332
21581
  if (!globalSDKInstance) {
21333
21582
  globalSDKInstance = new CustomerServiceSDK();
21334
21583
  }
21335
- return await globalSDKInstance.init(config, options);
21584
+ return await globalSDKInstance.init(config, options, forceReinit);
21336
21585
  };
21337
21586
  /**
21338
21587
  * 获取全局SDK实例
@@ -21441,6 +21690,16 @@ const updateScreenshotOptions = (options) => {
21441
21690
  const sdk = getInstance();
21442
21691
  sdk.updateScreenshotOptions(options);
21443
21692
  };
21693
+ /**
21694
+ * 更新 token(用于用户登录/退出场景)
21695
+ * @param token 新的 token(传空字符串或 undefined 表示移除 token)
21696
+ * @param options UI选项(可选)
21697
+ * @returns 返回更新后的初始化信息
21698
+ */
21699
+ const updateToken = async (token, options) => {
21700
+ const sdk = getInstance();
21701
+ return await sdk.updateToken(token, options);
21702
+ };
21444
21703
  // 默认导出
21445
21704
  var index = {
21446
21705
  init,
@@ -21463,6 +21722,7 @@ var index = {
21463
21722
  enableScreenshot,
21464
21723
  getScreenshotState,
21465
21724
  updateScreenshotOptions,
21725
+ updateToken,
21466
21726
  destroy
21467
21727
  };
21468
21728
 
@@ -21489,3 +21749,4 @@ exports.setScreenshotTarget = setScreenshotTarget;
21489
21749
  exports.showIcon = showIcon;
21490
21750
  exports.showNotification = showNotification;
21491
21751
  exports.updateScreenshotOptions = updateScreenshotOptions;
21752
+ exports.updateToken = updateToken;