customer-chat-sdk 1.1.5 → 1.1.7

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.
@@ -182,7 +182,7 @@ class IconManager {
182
182
  if (this.iconElement) {
183
183
  this.iconElement.remove();
184
184
  this.iconElement = null;
185
- this.onClickCallback = null;
185
+ // 注意:不清空 onClickCallback,以便再次显示时能继续使用
186
186
  this.dragMoveHandler = null;
187
187
  this.dragEndHandler = null;
188
188
  if (this.debug) {
@@ -601,7 +601,6 @@ class IconManager {
601
601
  class IframeManager {
602
602
  constructor(config = {}) {
603
603
  this.iframeElement = null;
604
- this.overlayElement = null;
605
604
  this.containerElement = null; // 包装容器,包含iframe和关闭按钮
606
605
  this.isOpen = false;
607
606
  this.isCreated = false;
@@ -623,18 +622,11 @@ class IframeManager {
623
622
  */
624
623
  async init() {
625
624
  try {
626
- // 关键修复:在初始化前,先清理页面上所有旧的遮罩层和容器元素
625
+ // 关键修复:在初始化前,先清理页面上所有旧的容器元素
627
626
  // 防止切换模式或多次初始化时产生重复的元素
628
627
  this.cleanupOrphanedElements();
629
- // PC模式:预创建遮罩层(隐藏状态),避免后续移动DOM导致iframe重新加载
630
- const actualMode = this.getActualMode();
631
- const isPC = actualMode === 'popup';
632
- if (isPC) {
633
- // 创建遮罩层但不立即显示(showImmediately = false)
634
- this.createOverlay(false);
635
- }
636
628
  // 创建隐藏的iframe(预连接到SSE)
637
- // 注意:createIframe 会将容器添加到遮罩层(PC模式)或 body(移动端)
629
+ // 注意:createIframe 会将容器添加到 body
638
630
  this.createIframe();
639
631
  this.isCreated = true;
640
632
  if (this.debug) {
@@ -658,70 +650,24 @@ class IframeManager {
658
650
  throw new Error('Iframe not initialized. Call init() first.');
659
651
  }
660
652
  try {
661
- const actualMode = this.getActualMode();
662
- const isPC = actualMode === 'popup';
663
- // PC模式:创建或显示遮罩层
664
- if (isPC) {
665
- // 如果遮罩层不存在,创建并显示;如果已存在,直接显示
666
- this.createOverlay(true); // showImmediately = true,立即显示
667
- }
668
653
  // 显示已创建的容器
669
654
  // 关键优化:容器在 init() 时已经添加到正确位置,这里只改变样式,不移动 DOM
670
655
  // 这样可以避免 iframe 重新加载
671
656
  if (this.containerElement) {
672
657
  // 确保容器在 DOM 中(理论上已经在 init() 时添加,但为了安全)
673
658
  if (!this.containerElement.parentNode) {
674
- // 如果容器不在 DOM 中(异常情况),根据模式添加到正确位置
675
- if (isPC && this.overlayElement) {
676
- // PC模式:确保遮罩层在 DOM 中,然后将容器添加到遮罩层
677
- if (!this.overlayElement.parentNode) {
678
- document.body.appendChild(this.overlayElement);
679
- }
680
- this.overlayElement.appendChild(this.containerElement);
681
- }
682
- else {
683
- // 移动端模式:直接添加到 body
684
- document.body.appendChild(this.containerElement);
685
- }
686
- }
687
- // 注意:不再检查容器是否在遮罩层内,因为 init() 时已经正确放置
688
- // 如果移动容器会导致 iframe 重新加载,所以不移动
689
- if (isPC) {
690
- // PC模式:配置为居中弹窗样式
691
- Object.assign(this.containerElement.style, {
692
- position: 'fixed',
693
- top: '50%',
694
- left: '50%',
695
- transform: 'translate(-50%, -50%)',
696
- visibility: 'visible',
697
- opacity: '1',
698
- display: 'block'
699
- });
700
- // 显示遮罩层(确保在 DOM 中)
701
- if (this.overlayElement) {
702
- // 确保遮罩层在 DOM 中
703
- if (!this.overlayElement.parentNode) {
704
- document.body.appendChild(this.overlayElement);
705
- }
706
- // 显示遮罩层(只改变样式,不移动)
707
- Object.assign(this.overlayElement.style, {
708
- visibility: 'visible',
709
- opacity: '1',
710
- display: 'flex'
711
- });
712
- }
713
- }
714
- else {
715
- // 移动端模式:直接全屏显示,不需要遮罩层
716
- Object.assign(this.containerElement.style, {
717
- position: 'fixed',
718
- visibility: 'visible',
719
- opacity: '1',
720
- display: 'block'
721
- });
722
- // 禁用body滚动,防止出现滚动条
723
- this.preventBodyScroll(true);
659
+ // 如果容器不在 DOM 中(异常情况),直接添加到 body
660
+ document.body.appendChild(this.containerElement);
724
661
  }
662
+ // PC和移动端都使用全屏模式
663
+ Object.assign(this.containerElement.style, {
664
+ position: 'fixed',
665
+ visibility: 'visible',
666
+ opacity: '1',
667
+ display: 'block'
668
+ });
669
+ // 禁用body滚动,防止出现滚动条
670
+ this.preventBodyScroll(true);
725
671
  }
726
672
  this.isOpen = true;
727
673
  if (this.debug) {
@@ -753,22 +699,10 @@ class IframeManager {
753
699
  opacity: '0',
754
700
  display: 'none'
755
701
  });
756
- // 注意:不移动容器,保持容器在当前位置(遮罩层或 body),避免 iframe 重新加载
757
- }
758
- // 隐藏遮罩层但不移除(仅PC模式,避免重新创建导致 iframe 重新加载)
759
- if (this.overlayElement) {
760
- Object.assign(this.overlayElement.style, {
761
- visibility: 'hidden',
762
- opacity: '0',
763
- display: 'none'
764
- });
765
- // 不移除遮罩层,下次显示时直接显示即可
766
- }
767
- // 恢复body滚动(移动端模式)
768
- const actualModeForScroll = this.getActualMode();
769
- if (actualModeForScroll === 'fullscreen') {
770
- this.preventBodyScroll(false);
702
+ // 注意:不移动容器,保持容器在当前位置(body),避免 iframe 重新加载
771
703
  }
704
+ // 恢复body滚动
705
+ this.preventBodyScroll(false);
772
706
  this.isOpen = false;
773
707
  if (this.debug) {
774
708
  console.log('CustomerSDK iframe hidden (SSE still connected)');
@@ -789,16 +723,12 @@ class IframeManager {
789
723
  */
790
724
  destroy() {
791
725
  this.hide();
792
- // 移除容器和遮罩层
726
+ // 移除容器
793
727
  if (this.containerElement) {
794
728
  this.containerElement.remove();
795
729
  this.containerElement = null;
796
730
  this.iframeElement = null;
797
731
  }
798
- if (this.overlayElement) {
799
- this.overlayElement.remove();
800
- this.overlayElement = null;
801
- }
802
732
  this.isCreated = false;
803
733
  if (this.debug) {
804
734
  console.log('CustomerSDK container destroyed');
@@ -819,19 +749,9 @@ class IframeManager {
819
749
  }
820
750
  }
821
751
  /**
822
- * 清理页面上孤立的遮罩层和容器元素(防止重复创建)
752
+ * 清理页面上孤立的容器元素(防止重复创建)
823
753
  */
824
754
  cleanupOrphanedElements() {
825
- // 清理所有旧的遮罩层元素(不属于当前实例的)
826
- const existingOverlays = document.querySelectorAll('.customer-sdk-overlay');
827
- existingOverlays.forEach((overlay) => {
828
- if (overlay !== this.overlayElement) {
829
- overlay.remove();
830
- if (this.debug) {
831
- console.log('清理旧的遮罩层元素');
832
- }
833
- }
834
- });
835
755
  // 清理所有旧的容器元素(不属于当前实例的)
836
756
  const existingContainers = document.querySelectorAll('.customer-sdk-container');
837
757
  existingContainers.forEach((container) => {
@@ -843,55 +763,6 @@ class IframeManager {
843
763
  }
844
764
  });
845
765
  }
846
- /**
847
- * 创建遮罩层(PC模式使用)
848
- * @param showImmediately 是否立即显示,默认 false(用于 init() 时创建但不显示)
849
- */
850
- createOverlay(showImmediately = false) {
851
- // 如果遮罩层已存在
852
- if (this.overlayElement) {
853
- // 如果不在 DOM 中,添加到 DOM
854
- if (!this.overlayElement.parentNode) {
855
- document.body.appendChild(this.overlayElement);
856
- }
857
- // 根据参数决定是否显示
858
- if (showImmediately) {
859
- Object.assign(this.overlayElement.style, {
860
- visibility: 'visible',
861
- opacity: '1',
862
- display: 'flex'
863
- });
864
- }
865
- return;
866
- }
867
- // 创建新的遮罩层
868
- this.overlayElement = document.createElement('div');
869
- this.overlayElement.className = 'customer-sdk-overlay';
870
- Object.assign(this.overlayElement.style, {
871
- position: 'fixed',
872
- top: '0',
873
- left: '0',
874
- width: '100%',
875
- height: '100%',
876
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
877
- zIndex: '999998',
878
- display: showImmediately ? 'flex' : 'none', // 根据参数决定初始显示状态
879
- alignItems: 'center',
880
- justifyContent: 'center',
881
- cursor: this.config.allowClose ? 'pointer' : 'default',
882
- visibility: showImmediately ? 'visible' : 'hidden',
883
- opacity: showImmediately ? '1' : '0'
884
- });
885
- // 点击遮罩层关闭(只添加一次事件监听器)
886
- if (this.config.allowClose) {
887
- this.overlayElement.addEventListener('click', (e) => {
888
- if (e.target === this.overlayElement) {
889
- this.close();
890
- }
891
- });
892
- }
893
- document.body.appendChild(this.overlayElement);
894
- }
895
766
  /**
896
767
  * 创建iframe(默认隐藏状态,用于SSE连接)
897
768
  */
@@ -937,25 +808,26 @@ class IframeManager {
937
808
  // 根据设备类型设置模式
938
809
  const actualMode = this.getActualMode();
939
810
  const isPC = actualMode === 'popup';
940
- this.iframeElement.scrolling = isPC ? 'auto' : 'no'; // PC显示滚动条,移动端禁用
941
- // PC 模式:使用配置的宽度和高度
942
- // 移动端:使用全屏
811
+ this.iframeElement.scrolling = 'auto'; // PC和移动端都显示滚动条
812
+ // PC模式:使用配置的宽度,高度100%;移动端:100%宽度和高度
943
813
  const containerStyles = isPC ? {
944
- // PC 弹窗模式
814
+ // PC模式:配置的宽度,高度100%
945
815
  width: `${this.config.width || 450}px`,
946
- height: `${this.config.height || 600}px`,
816
+ height: '100%',
947
817
  maxWidth: '90vw',
948
- maxHeight: '90vh',
818
+ maxHeight: '100%',
949
819
  backgroundColor: '#ffffff',
950
- borderRadius: '12px',
951
- boxShadow: '0 20px 40px rgba(0, 0, 0, 0.15)',
820
+ borderRadius: '0',
821
+ boxShadow: 'none',
952
822
  border: 'none',
953
823
  position: 'fixed',
954
824
  zIndex: '999999',
955
- // PC模式:居中显示
956
- top: '50%',
825
+ // PC模式:水平居中,垂直占满
826
+ top: '0',
957
827
  left: '50%',
958
- transform: 'translate(-50%, -50%)',
828
+ bottom: '0',
829
+ right: 'auto',
830
+ transform: 'translateX(-50%)',
959
831
  overflow: 'hidden',
960
832
  // 初始隐藏的关键样式
961
833
  visibility: 'hidden',
@@ -991,35 +863,20 @@ class IframeManager {
991
863
  width: '100%',
992
864
  height: '100%',
993
865
  border: 'none',
994
- borderRadius: 'inherit',
995
- ...(isPC ? {} : {
996
- // 移动端:彻底禁用滚动条
997
- overflow: 'hidden',
998
- scrollbarWidth: 'none', // Firefox
999
- msOverflowStyle: 'none', // IE/Edge
1000
- WebkitScrollbar: 'none' // WebKit (Chrome/Safari)
1001
- })
866
+ borderRadius: 'inherit'
1002
867
  };
1003
868
  Object.assign(this.iframeElement.style, iframeStyles);
1004
869
  // 将iframe放入容器
1005
870
  this.containerElement.appendChild(this.iframeElement);
1006
871
  // 添加iframe加载事件监听(移动端样式优化)
1007
872
  this.iframeElement.addEventListener('load', () => {
1008
- // 仅在移动端注入自定义样式
873
+ // 移动端注入自定义样式
1009
874
  if (!isPC) {
1010
875
  this.injectMobileStyles();
1011
876
  }
1012
877
  });
1013
- // 关键优化:PC模式将容器添加到遮罩层(如果遮罩层已创建),避免后续移动DOM导致iframe重新加载
1014
- // 移动端直接添加到body
1015
- if (isPC && this.overlayElement) {
1016
- // PC模式:添加到遮罩层(遮罩层已在 init() 中创建)
1017
- this.overlayElement.appendChild(this.containerElement);
1018
- }
1019
- else {
1020
- // 移动端模式:直接添加到body
1021
- document.body.appendChild(this.containerElement);
1022
- }
878
+ // 关键优化:容器直接添加到body,避免后续移动DOM导致iframe重新加载
879
+ document.body.appendChild(this.containerElement);
1023
880
  if (this.debug) {
1024
881
  console.log('CustomerSDK container created (hidden, ready for SSE)');
1025
882
  }
@@ -20936,19 +20793,33 @@ class CustomerServiceSDK {
20936
20793
  * 初始化 SDK
20937
20794
  * @param config SDK配置
20938
20795
  * @param options UI选项(可选)
20796
+ * @param forceReinit 是否强制重新初始化(用于更新token等配置)
20939
20797
  * @returns 返回初始化信息(包含设备ID等)
20940
20798
  */
20941
- async init(config, options) {
20942
- if (this.isInitialized) {
20799
+ async init(config, options, forceReinit = false) {
20800
+ // 如果已经初始化且不强制重新初始化,返回之前保存的初始化信息
20801
+ if (this.isInitialized && !forceReinit) {
20943
20802
  if (this.debug) {
20944
- console.warn('CustomerSDK already initialized');
20803
+ console.warn('CustomerSDK already initialized, returning cached result. Use forceReinit=true to reinitialize.');
20945
20804
  }
20946
- // 如果已经初始化,返回之前保存的初始化信息
20947
20805
  if (this.initResult) {
20948
20806
  return this.initResult;
20949
20807
  }
20950
20808
  throw new Error('SDK already initialized but cannot retrieve initialization info');
20951
20809
  }
20810
+ // 如果需要强制重新初始化,先清理旧资源
20811
+ if (this.isInitialized && forceReinit) {
20812
+ if (this.debug) {
20813
+ console.log('Force reinitializing SDK...');
20814
+ }
20815
+ // 清理旧的 iframe 和截图管理器,但保留图标管理器(保持图标位置等状态)
20816
+ this.iframeManager?.destroy();
20817
+ this.screenshotManager?.destroy();
20818
+ this.iframeManager = null;
20819
+ this.screenshotManager = null;
20820
+ // 重置初始化标志,但保留图标管理器
20821
+ this.isInitialized = false;
20822
+ }
20952
20823
  this.config = config;
20953
20824
  this.debug = config.debug ?? false;
20954
20825
  try {
@@ -20989,9 +20860,9 @@ class CustomerServiceSDK {
20989
20860
  // checkScreenshot 消息由 ScreenshotManager 处理,不需要在这里处理
20990
20861
  },
20991
20862
  onClose: () => {
20992
- // iframe关闭时,清理图标拖动事件监听器,并重新启用图标点击
20863
+ // iframe关闭时,清理图标拖动事件监听器,并重新显示图标
20993
20864
  this.iconManager?.forceCleanupDragEvents();
20994
- this.iconManager?.enableClick();
20865
+ this.iconManager?.show();
20995
20866
  },
20996
20867
  ...options
20997
20868
  });
@@ -21001,9 +20872,9 @@ class CustomerServiceSDK {
21001
20872
  this.iconManager.onClick(() => {
21002
20873
  // 打开iframe时清除红点通知
21003
20874
  this.clearNotification();
20875
+ // 点击图标后隐藏图标
20876
+ this.iconManager?.hide();
21004
20877
  this.iframeManager?.show();
21005
- // iframe 打开后,禁用图标点击(防止重复打开)
21006
- this.iconManager?.disableClick();
21007
20878
  });
21008
20879
  // 初始化截图管理器(如果启用了截图功能)
21009
20880
  if (config.screenshot) {
@@ -21182,6 +21053,31 @@ class CustomerServiceSDK {
21182
21053
  console.log('📸 截图配置已更新:', options);
21183
21054
  }
21184
21055
  }
21056
+ /**
21057
+ * 更新 token(用于用户登录/退出场景)
21058
+ * 如果已初始化,会重新创建 iframe 并更新 URL
21059
+ * @param token 新的 token(传空字符串或 undefined 表示移除 token)
21060
+ * @param options UI选项(可选,用于重新初始化时的配置)
21061
+ * @returns 返回更新后的初始化信息
21062
+ */
21063
+ async updateToken(token, options) {
21064
+ if (!this.isInitialized) {
21065
+ throw new Error('SDK not initialized. Call init() first.');
21066
+ }
21067
+ if (!this.config) {
21068
+ throw new Error('SDK config not found');
21069
+ }
21070
+ // 更新配置中的 token
21071
+ const updatedConfig = {
21072
+ ...this.config,
21073
+ token: token && token.trim() !== '' ? token : undefined
21074
+ };
21075
+ if (this.debug) {
21076
+ console.log('Updating token:', token ? 'Token provided' : 'Token removed');
21077
+ }
21078
+ // 强制重新初始化以应用新的 token
21079
+ return await this.init(updatedConfig, options, true);
21080
+ }
21185
21081
  /**
21186
21082
  * 销毁 SDK
21187
21083
  */
@@ -21265,13 +21161,14 @@ let globalSDKInstance = null;
21265
21161
  * 初始化 Customer SDK
21266
21162
  * @param config SDK配置
21267
21163
  * @param options UI选项(可选)
21164
+ * @param forceReinit 是否强制重新初始化(用于更新token等配置)
21268
21165
  * @returns 返回初始化信息(包含设备ID等)
21269
21166
  */
21270
- const init = async (config, options) => {
21167
+ const init = async (config, options, forceReinit = false) => {
21271
21168
  if (!globalSDKInstance) {
21272
21169
  globalSDKInstance = new CustomerServiceSDK();
21273
21170
  }
21274
- return await globalSDKInstance.init(config, options);
21171
+ return await globalSDKInstance.init(config, options, forceReinit);
21275
21172
  };
21276
21173
  /**
21277
21174
  * 获取全局SDK实例
@@ -21380,6 +21277,16 @@ const updateScreenshotOptions = (options) => {
21380
21277
  const sdk = getInstance();
21381
21278
  sdk.updateScreenshotOptions(options);
21382
21279
  };
21280
+ /**
21281
+ * 更新 token(用于用户登录/退出场景)
21282
+ * @param token 新的 token(传空字符串或 undefined 表示移除 token)
21283
+ * @param options UI选项(可选)
21284
+ * @returns 返回更新后的初始化信息
21285
+ */
21286
+ const updateToken = async (token, options) => {
21287
+ const sdk = getInstance();
21288
+ return await sdk.updateToken(token, options);
21289
+ };
21383
21290
  // 默认导出
21384
21291
  var index = {
21385
21292
  init,
@@ -21402,7 +21309,8 @@ var index = {
21402
21309
  enableScreenshot,
21403
21310
  getScreenshotState,
21404
21311
  updateScreenshotOptions,
21312
+ updateToken,
21405
21313
  destroy
21406
21314
  };
21407
21315
 
21408
- export { CustomerServiceSDK, captureScreenshot, clearNotification, closeChat, index as default, destroy, enableScreenshot, getConnectionStatus, getInitResult, getInstance, getScreenshotState, hideIcon, init, isChatOpen, openChat, sendToIframe, setIconCoordinates, setIconPosition, setIconStyle, setScreenshotTarget, showIcon, showNotification, updateScreenshotOptions };
21316
+ export { CustomerServiceSDK, captureScreenshot, clearNotification, closeChat, index as default, destroy, enableScreenshot, getConnectionStatus, getInitResult, getInstance, getScreenshotState, hideIcon, init, isChatOpen, openChat, sendToIframe, setIconCoordinates, setIconPosition, setIconStyle, setScreenshotTarget, showIcon, showNotification, updateScreenshotOptions, updateToken };