customer-chat-sdk 1.1.9 → 1.1.10

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.
@@ -720,6 +720,7 @@ class IframeManager {
720
720
  this.isCreated = false;
721
721
  this.debug = false; // debug 模式标志
722
722
  this.targetElement = null; // 目标元素(用于自适应宽度)
723
+ this.messageHandler = null; // 消息监听器引用(用于清理)
723
724
  this.config = {
724
725
  src: '',
725
726
  mode: 'auto', // 默认自动检测设备类型
@@ -729,7 +730,8 @@ class IframeManager {
729
730
  ...config
730
731
  };
731
732
  this.debug = config.debug ?? false;
732
- this.setupMessageListener();
733
+ // 不在构造函数中添加消息监听器,延迟到 init() 中添加
734
+ // 这样可以避免创建多个实例时产生多个监听器
733
735
  }
734
736
  /**
735
737
  * 初始化iframe(隐藏状态)
@@ -737,6 +739,8 @@ class IframeManager {
737
739
  */
738
740
  async init() {
739
741
  try {
742
+ // 设置消息监听器(在 init() 中而不是构造函数中,避免多次创建实例时产生多个监听器)
743
+ this.setupMessageListener();
740
744
  // 关键修复:在初始化前,先清理页面上所有旧的容器元素
741
745
  // 防止切换模式或多次初始化时产生重复的元素
742
746
  this.cleanupOrphanedElements();
@@ -751,6 +755,11 @@ class IframeManager {
751
755
  catch (error) {
752
756
  // 错误始终输出
753
757
  console.error('Failed to initialize iframe:', error);
758
+ // 如果初始化失败,清理消息监听器
759
+ if (this.messageHandler) {
760
+ window.removeEventListener('message', this.messageHandler);
761
+ this.messageHandler = null;
762
+ }
754
763
  throw error;
755
764
  }
756
765
  }
@@ -838,6 +847,11 @@ class IframeManager {
838
847
  */
839
848
  destroy() {
840
849
  this.hide();
850
+ // 移除消息监听器(防止内存泄漏)
851
+ if (this.messageHandler) {
852
+ window.removeEventListener('message', this.messageHandler);
853
+ this.messageHandler = null;
854
+ }
841
855
  // 移除容器
842
856
  if (this.containerElement) {
843
857
  this.containerElement.remove();
@@ -1165,12 +1179,18 @@ class IframeManager {
1165
1179
  * 设置消息监听
1166
1180
  */
1167
1181
  setupMessageListener() {
1168
- window.addEventListener('message', (event) => {
1182
+ // 如果已存在,先移除旧的监听器(防止重复添加)
1183
+ if (this.messageHandler) {
1184
+ window.removeEventListener('message', this.messageHandler);
1185
+ }
1186
+ // 创建新的消息处理器并保存引用
1187
+ this.messageHandler = (event) => {
1169
1188
  // 验证消息来源(可选的安全检查)
1170
1189
  if (!this.config.src || event.origin === new URL(this.config.src).origin) {
1171
1190
  this.handleIframeMessage(event.data);
1172
1191
  }
1173
- }, false);
1192
+ };
1193
+ window.addEventListener('message', this.messageHandler, false);
1174
1194
  }
1175
1195
  /**
1176
1196
  * 处理来自iframe的消息
@@ -20983,26 +21003,49 @@ class CustomerServiceSDK {
20983
21003
  this.screenshotManager = null;
20984
21004
  this.config = null;
20985
21005
  this.isInitialized = false;
21006
+ this.isInitializing = false; // 初始化锁,防止并发初始化
20986
21007
  this.initResult = null; // 保存初始化结果
20987
21008
  this.debug = false; // debug 模式标志
21009
+ this.lastIconConfig = null; // 保存上一次的图标配置
20988
21010
  }
20989
21011
  /**
20990
21012
  * 初始化 SDK
20991
21013
  * @param config SDK配置
20992
21014
  * @param options UI选项(可选)
21015
+ * @param forceReinit 是否强制重新初始化(用于更新token等配置)
20993
21016
  * @returns 返回初始化信息(包含设备ID等)
20994
21017
  */
20995
- async init(config, options) {
20996
- if (this.isInitialized) {
21018
+ async init(config, options, forceReinit = false) {
21019
+ // 防止并发初始化
21020
+ if (this.isInitializing) {
21021
+ throw new Error('SDK is already initializing. Please wait for the current initialization to complete.');
21022
+ }
21023
+ // 如果已经初始化且不强制重新初始化,返回之前保存的初始化信息
21024
+ if (this.isInitialized && !forceReinit) {
20997
21025
  if (this.debug) {
20998
- console.warn('CustomerSDK already initialized');
21026
+ console.warn('CustomerSDK already initialized, returning cached result. Use forceReinit=true to reinitialize.');
20999
21027
  }
21000
- // 如果已经初始化,返回之前保存的初始化信息
21001
21028
  if (this.initResult) {
21002
21029
  return this.initResult;
21003
21030
  }
21004
21031
  throw new Error('SDK already initialized but cannot retrieve initialization info');
21005
21032
  }
21033
+ // 设置初始化锁
21034
+ this.isInitializing = true;
21035
+ // 如果需要强制重新初始化,先清理旧资源
21036
+ if (this.isInitialized && forceReinit) {
21037
+ if (this.debug) {
21038
+ console.log('Force reinitializing SDK...');
21039
+ }
21040
+ // 清理旧的 iframe 和截图管理器
21041
+ this.iframeManager?.destroy();
21042
+ this.screenshotManager?.destroy();
21043
+ this.iframeManager = null;
21044
+ this.screenshotManager = null;
21045
+ // 注意:图标管理器暂时保留,稍后检查配置是否变化再决定是否重新创建
21046
+ // 重置初始化标志
21047
+ this.isInitialized = false;
21048
+ }
21006
21049
  this.config = config;
21007
21050
  this.debug = config.debug ?? false;
21008
21051
  try {
@@ -21021,11 +21064,47 @@ class CustomerServiceSDK {
21021
21064
  agent: config.agent,
21022
21065
  timestamp: Date.now()
21023
21066
  };
21024
- // 创建悬浮图标管理器(支持自定义位置和传送目标)
21025
- const iconPosition = options?.iconPosition || undefined;
21026
- const iconTarget = options?.target || undefined;
21027
- this.iconManager = new IconManager(iconPosition, this.debug, iconTarget);
21028
- await this.iconManager.show();
21067
+ // 处理图标管理器(优化:如果配置没变化,保留图标避免闪烁)
21068
+ // 如果 options 未提供,使用之前保存的配置(避免更新 token 时图标闪烁)
21069
+ const iconPosition = options?.iconPosition !== undefined
21070
+ ? options.iconPosition
21071
+ : (this.lastIconConfig?.position || undefined);
21072
+ const iconTarget = options?.target !== undefined
21073
+ ? options.target
21074
+ : (this.lastIconConfig?.target || undefined);
21075
+ // 检查图标配置是否变化
21076
+ const iconConfigChanged = this.isIconConfigChanged(iconPosition, iconTarget);
21077
+ if (iconConfigChanged) {
21078
+ // 配置变化了,需要重新创建图标管理器
21079
+ if (this.iconManager) {
21080
+ this.iconManager.hide();
21081
+ this.iconManager = null; // 先清空引用,避免引用混乱
21082
+ if (this.debug) {
21083
+ console.log('Icon config changed, recreating icon manager');
21084
+ }
21085
+ }
21086
+ this.iconManager = new IconManager(iconPosition, this.debug, iconTarget);
21087
+ await this.iconManager.show();
21088
+ // 保存新的配置
21089
+ this.lastIconConfig = { position: iconPosition, target: iconTarget };
21090
+ }
21091
+ else {
21092
+ // 配置没变化,保留图标管理器(避免闪烁)
21093
+ if (!this.iconManager) {
21094
+ // 如果不存在,创建新的
21095
+ this.iconManager = new IconManager(iconPosition, this.debug, iconTarget);
21096
+ await this.iconManager.show();
21097
+ }
21098
+ else {
21099
+ // 已存在,确保显示(可能被隐藏了)
21100
+ await this.iconManager.show();
21101
+ if (this.debug) {
21102
+ console.log('Icon config unchanged, keeping existing icon manager');
21103
+ }
21104
+ }
21105
+ // 更新配置记录
21106
+ this.lastIconConfig = { position: iconPosition, target: iconTarget };
21107
+ }
21029
21108
  // 创建iframe管理器(自动检测设备类型)
21030
21109
  // 如果提供了 target,iframe 会自适应 target 的宽度(PC 和移动端都适用)
21031
21110
  // 如果没有 target,PC 模式使用默认宽度,移动端全屏
@@ -21094,8 +21173,33 @@ class CustomerServiceSDK {
21094
21173
  catch (error) {
21095
21174
  // 错误始终输出
21096
21175
  console.error('Failed to initialize CustomerSDK:', error);
21176
+ // 清理已创建的资源(防止资源泄漏)
21177
+ try {
21178
+ if (this.iconManager) {
21179
+ this.iconManager.hide();
21180
+ this.iconManager = null;
21181
+ }
21182
+ if (this.iframeManager) {
21183
+ this.iframeManager.destroy();
21184
+ this.iframeManager = null;
21185
+ }
21186
+ if (this.screenshotManager) {
21187
+ this.screenshotManager.destroy();
21188
+ this.screenshotManager = null;
21189
+ }
21190
+ this.isInitialized = false;
21191
+ this.initResult = null;
21192
+ }
21193
+ catch (cleanupError) {
21194
+ // 清理过程中的错误不应该影响原始错误的抛出
21195
+ console.error('Error during cleanup after initialization failure:', cleanupError);
21196
+ }
21097
21197
  throw error;
21098
21198
  }
21199
+ finally {
21200
+ // 释放初始化锁
21201
+ this.isInitializing = false;
21202
+ }
21099
21203
  }
21100
21204
  /**
21101
21205
  * 显示/隐藏悬浮图标
@@ -21239,18 +21343,44 @@ class CustomerServiceSDK {
21239
21343
  console.log('📸 截图配置已更新:', options);
21240
21344
  }
21241
21345
  }
21346
+ /**
21347
+ * 更新 token(用于用户登录/退出场景)
21348
+ * 如果已初始化,会重新创建 iframe 并更新 URL
21349
+ * @param token 新的 token(传空字符串或 undefined 表示移除 token)
21350
+ * @param options UI选项(可选,用于重新初始化时的配置)
21351
+ * @returns 返回更新后的初始化信息
21352
+ */
21353
+ async updateToken(token, options) {
21354
+ if (!this.isInitialized) {
21355
+ throw new Error('SDK not initialized. Call init() first.');
21356
+ }
21357
+ if (!this.config) {
21358
+ throw new Error('SDK config not found');
21359
+ }
21360
+ // 更新配置中的 token
21361
+ const updatedConfig = {
21362
+ ...this.config,
21363
+ token: token && token.trim() !== '' ? token : undefined
21364
+ };
21365
+ if (this.debug) {
21366
+ console.log('Updating token:', token ? 'Token provided' : 'Token removed');
21367
+ }
21368
+ // 强制重新初始化以应用新的 token
21369
+ return await this.init(updatedConfig, options, true);
21370
+ }
21242
21371
  /**
21243
21372
  * 销毁 SDK
21244
21373
  */
21245
21374
  destroy() {
21246
21375
  this.iconManager?.hide();
21247
- this.iframeManager?.close();
21376
+ this.iframeManager?.destroy(); // 使用 destroy 而不是 close,确保完全清理
21248
21377
  this.screenshotManager?.destroy();
21249
21378
  this.iconManager = null;
21250
21379
  this.iframeManager = null;
21251
21380
  this.screenshotManager = null;
21252
21381
  this.config = null;
21253
21382
  this.initResult = null;
21383
+ this.lastIconConfig = null;
21254
21384
  this.isInitialized = false;
21255
21385
  if (this.debug) {
21256
21386
  console.log('CustomerSDK destroyed');
@@ -21295,6 +21425,36 @@ class CustomerServiceSDK {
21295
21425
  return fallbackId;
21296
21426
  }
21297
21427
  }
21428
+ /**
21429
+ * 检查图标配置是否变化
21430
+ */
21431
+ isIconConfigChanged(newPosition, newTarget) {
21432
+ // 如果没有保存的配置,说明是首次初始化,需要创建
21433
+ if (!this.lastIconConfig) {
21434
+ return true;
21435
+ }
21436
+ // 比较位置配置
21437
+ const positionChanged = JSON.stringify(this.lastIconConfig.position) !== JSON.stringify(newPosition);
21438
+ // 比较 target 配置
21439
+ const oldTarget = this.lastIconConfig.target;
21440
+ let targetChanged = false;
21441
+ if (oldTarget !== newTarget) {
21442
+ // 如果引用不同,进一步检查
21443
+ if (typeof oldTarget === 'string' && typeof newTarget === 'string') {
21444
+ // 都是字符串,比较值
21445
+ targetChanged = oldTarget !== newTarget;
21446
+ }
21447
+ else if (oldTarget instanceof HTMLElement && newTarget instanceof HTMLElement) {
21448
+ // 都是 HTMLElement,比较引用(引用不同就是变化了)
21449
+ targetChanged = true;
21450
+ }
21451
+ else {
21452
+ // 类型不同,肯定是变化了
21453
+ targetChanged = true;
21454
+ }
21455
+ }
21456
+ return positionChanged || targetChanged;
21457
+ }
21298
21458
  /**
21299
21459
  * 构建iframe URL(带用户参数和设备ID)
21300
21460
  */
@@ -21322,13 +21482,14 @@ let globalSDKInstance = null;
21322
21482
  * 初始化 Customer SDK
21323
21483
  * @param config SDK配置
21324
21484
  * @param options UI选项(可选)
21485
+ * @param forceReinit 是否强制重新初始化(用于更新token等配置)
21325
21486
  * @returns 返回初始化信息(包含设备ID等)
21326
21487
  */
21327
- const init = async (config, options) => {
21488
+ const init = async (config, options, forceReinit = false) => {
21328
21489
  if (!globalSDKInstance) {
21329
21490
  globalSDKInstance = new CustomerServiceSDK();
21330
21491
  }
21331
- return await globalSDKInstance.init(config, options);
21492
+ return await globalSDKInstance.init(config, options, forceReinit);
21332
21493
  };
21333
21494
  /**
21334
21495
  * 获取全局SDK实例
@@ -21437,6 +21598,16 @@ const updateScreenshotOptions = (options) => {
21437
21598
  const sdk = getInstance();
21438
21599
  sdk.updateScreenshotOptions(options);
21439
21600
  };
21601
+ /**
21602
+ * 更新 token(用于用户登录/退出场景)
21603
+ * @param token 新的 token(传空字符串或 undefined 表示移除 token)
21604
+ * @param options UI选项(可选)
21605
+ * @returns 返回更新后的初始化信息
21606
+ */
21607
+ const updateToken = async (token, options) => {
21608
+ const sdk = getInstance();
21609
+ return await sdk.updateToken(token, options);
21610
+ };
21440
21611
  // 默认导出
21441
21612
  var index = {
21442
21613
  init,
@@ -21459,7 +21630,8 @@ var index = {
21459
21630
  enableScreenshot,
21460
21631
  getScreenshotState,
21461
21632
  updateScreenshotOptions,
21633
+ updateToken,
21462
21634
  destroy
21463
21635
  };
21464
21636
 
21465
- 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 };
21637
+ 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 };