customer-chat-sdk 1.0.66 → 1.0.68

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.
@@ -14334,6 +14334,10 @@ class ScreenshotManager {
14334
14334
  this.dynamicInterval = null;
14335
14335
  // 过期定时器
14336
14336
  this.expirationTimer = null;
14337
+ // 当前任务完成标志(用于串行定时任务)
14338
+ this.isCurrentTaskCompleted = true;
14339
+ // 保存 scheduleNext 函数引用(用于在压缩完成后触发下一次任务)
14340
+ this.scheduleNextFn = null;
14337
14341
  // 图片代理缓存(带过期时间)
14338
14342
  this.imageProxyCache = new Map();
14339
14343
  // IndexedDB 缓存(持久化)
@@ -14676,15 +14680,40 @@ class ScreenshotManager {
14676
14680
  this.worker = this.createWorker();
14677
14681
  }
14678
14682
  // 设置定时器(使用递归 setTimeout,确保等待前一个完成)
14679
- // 这样可以避免 setInterval 不等待异步完成的问题
14683
+ // 串行执行:必须等待上次任务完成后才进行下次任务
14680
14684
  const scheduleNext = async () => {
14685
+ // 如果上次任务还没完成,等待完成
14686
+ if (!this.isCurrentTaskCompleted) {
14687
+ if (!this.options.silentMode) {
14688
+ console.log('📸 [定时] 等待上次任务完成...');
14689
+ }
14690
+ // 每100ms检查一次任务是否完成
14691
+ const checkInterval = setInterval(() => {
14692
+ if (this.isCurrentTaskCompleted && this.isRunning) {
14693
+ clearInterval(checkInterval);
14694
+ scheduleNext();
14695
+ }
14696
+ else if (!this.isRunning) {
14697
+ clearInterval(checkInterval);
14698
+ }
14699
+ }, 100);
14700
+ return;
14701
+ }
14681
14702
  if (this.isRunning && this.isEnabled && !document.hidden) {
14703
+ // 标记任务开始
14704
+ this.isCurrentTaskCompleted = false;
14705
+ // 记录定时开始时间
14706
+ const scheduleStartTime = performance.now();
14707
+ if (!this.options.silentMode) {
14708
+ console.log(`📸 [定时开始] 开始新一轮截图任务`);
14709
+ }
14682
14710
  try {
14683
- await this.takeScreenshot();
14711
+ await this.takeScreenshot(scheduleStartTime);
14684
14712
  // 如果配置了二进制模式,发送二进制数据
14685
14713
  // 注意:如果启用了压缩,会在 Worker 压缩完成后自动发送(在 onmessage 中处理)
14686
14714
  // 如果没有启用压缩,立即发送
14687
14715
  if (this.currentBinaryConfig && !this.options.compress) {
14716
+ const sendStartTime = performance.now();
14688
14717
  const latestScreenshot = this.getLatestScreenshot();
14689
14718
  if (latestScreenshot) {
14690
14719
  try {
@@ -14692,14 +14721,20 @@ class ScreenshotManager {
14692
14721
  const base64Data = latestScreenshot.split(',')[1] || '';
14693
14722
  const base64Size = base64Data.length;
14694
14723
  // 将截图转换为 ArrayBuffer
14724
+ const convertStartTime = performance.now();
14695
14725
  const imageBuffer = this.dataUrlToArrayBuffer(latestScreenshot);
14696
14726
  const imageBufferSize = imageBuffer.byteLength;
14727
+ const convertTime = performance.now() - convertStartTime;
14697
14728
  // 构建配置的二进制结构
14729
+ const buildConfigStartTime = performance.now();
14698
14730
  const configBuffer = this.buildBinaryConfig(this.currentBinaryConfig);
14699
14731
  const configBufferSize = configBuffer.byteLength;
14732
+ const buildConfigTime = performance.now() - buildConfigStartTime;
14700
14733
  // 合并配置字节和图片字节(配置在前)
14734
+ const combineStartTime = performance.now();
14701
14735
  const combinedBuffer = this.combineBinaryData(configBuffer, imageBuffer);
14702
14736
  const combinedBufferSize = combinedBuffer.byteLength;
14737
+ const combineTime = performance.now() - combineStartTime;
14703
14738
  // 打印大小信息
14704
14739
  if (!this.options.silentMode) {
14705
14740
  console.log('📸 [轮询-大小统计]');
@@ -14707,16 +14742,26 @@ class ScreenshotManager {
14707
14742
  console.log(` 图片字节大小: ${(imageBufferSize / 1024).toFixed(2)} KB (${imageBufferSize} 字节)`);
14708
14743
  console.log(` 配置字节大小: ${configBufferSize} 字节`);
14709
14744
  console.log(` 拼接后总大小: ${(combinedBufferSize / 1024).toFixed(2)} KB (${combinedBufferSize} 字节)`);
14745
+ console.log(` ⏱️ 数据转换耗时: ${convertTime.toFixed(2)}ms`);
14746
+ console.log(` ⏱️ 构建配置耗时: ${buildConfigTime.toFixed(2)}ms`);
14747
+ console.log(` ⏱️ 合并数据耗时: ${combineTime.toFixed(2)}ms`);
14710
14748
  }
14711
14749
  // 发送二进制数据到 iframe
14712
14750
  if (this.sendToIframeCallback) {
14751
+ const sendCallbackStartTime = performance.now();
14713
14752
  const message = {
14714
14753
  type: 'screenshotBinary',
14715
14754
  data: combinedBuffer
14716
14755
  };
14717
14756
  this.sendToIframeCallback(message);
14757
+ const sendCallbackTime = performance.now() - sendCallbackStartTime;
14758
+ const totalSendTime = performance.now() - sendStartTime;
14759
+ const totalTime = performance.now() - scheduleStartTime;
14718
14760
  if (!this.options.silentMode) {
14719
14761
  console.log('📸 [轮询] ✅ 二进制数据已发送到 iframe');
14762
+ console.log(` ⏱️ 发送回调耗时: ${sendCallbackTime.toFixed(2)}ms`);
14763
+ console.log(` ⏱️ 发送阶段总耗时: ${totalSendTime.toFixed(2)}ms`);
14764
+ console.log(` ⏱️ 从定时开始到发送完成总耗时: ${totalTime.toFixed(2)}ms`);
14720
14765
  }
14721
14766
  }
14722
14767
  }
@@ -14724,25 +14769,36 @@ class ScreenshotManager {
14724
14769
  console.error('📸 [轮询] ❌ 处理二进制数据失败:', error);
14725
14770
  }
14726
14771
  }
14772
+ // 任务完成(无压缩模式)
14773
+ this.isCurrentTaskCompleted = true;
14727
14774
  }
14728
14775
  else if (this.currentBinaryConfig && this.options.compress) {
14729
14776
  // 启用了压缩,等待 Worker 压缩完成后在 onmessage 中发送
14777
+ // 任务完成标志会在压缩完成的回调中设置
14730
14778
  if (!this.options.silentMode) {
14731
14779
  console.log('📸 [轮询] 等待 Worker 压缩完成后发送到 iframe...');
14732
14780
  }
14733
14781
  }
14782
+ else {
14783
+ // 没有二进制配置,任务完成
14784
+ this.isCurrentTaskCompleted = true;
14785
+ }
14734
14786
  }
14735
14787
  catch (error) {
14736
14788
  if (!this.options.silentMode) {
14737
14789
  console.error('📸 [轮询] 截图失败:', error);
14738
14790
  }
14791
+ // 任务失败,标记为完成
14792
+ this.isCurrentTaskCompleted = true;
14739
14793
  }
14740
14794
  }
14741
- // 如果还在运行,安排下一次截图
14742
- if (this.isRunning) {
14795
+ // 如果还在运行且任务已完成,安排下一次截图
14796
+ if (this.isRunning && this.isCurrentTaskCompleted) {
14743
14797
  this.screenshotTimer = setTimeout(scheduleNext, currentInterval);
14744
14798
  }
14745
14799
  };
14800
+ // 保存 scheduleNext 函数引用,以便在压缩完成的回调中使用
14801
+ this.scheduleNextFn = scheduleNext;
14746
14802
  // 立即开始第一次
14747
14803
  scheduleNext();
14748
14804
  // 注意:不再立即执行一次,因为已经在 takeScreenshotAndUpload 中执行了
@@ -14762,6 +14818,9 @@ class ScreenshotManager {
14762
14818
  clearInterval(this.screenshotTimer);
14763
14819
  this.screenshotTimer = null;
14764
14820
  }
14821
+ // 清理任务状态
14822
+ this.isCurrentTaskCompleted = true;
14823
+ this.scheduleNextFn = null;
14765
14824
  }
14766
14825
  /**
14767
14826
  * 手动截图一次(允许在未启用时也执行,用于测试)
@@ -14776,25 +14835,41 @@ class ScreenshotManager {
14776
14835
  /**
14777
14836
  * 执行截图
14778
14837
  */
14779
- async takeScreenshot() {
14838
+ async takeScreenshot(scheduleStartTime) {
14780
14839
  if (!this.targetElement) {
14781
14840
  console.warn('📸 目标元素不存在');
14782
14841
  return false;
14783
14842
  }
14843
+ // 记录截图开始时间
14844
+ const screenshotStartTime = performance.now();
14784
14845
  this.setupGlobalErrorHandlers();
14785
14846
  try {
14786
14847
  if (!this.options.silentMode) {
14787
14848
  console.log(`📸 开始截图 #${this.screenshotCount + 1}...`);
14849
+ if (scheduleStartTime) {
14850
+ const waitTime = screenshotStartTime - scheduleStartTime;
14851
+ console.log(` ⏱️ 定时到截图开始耗时: ${waitTime.toFixed(2)}ms`);
14852
+ }
14788
14853
  }
14789
14854
  // 等待 CSS 和字体加载完成
14855
+ const waitStylesStartTime = performance.now();
14790
14856
  await Promise.all([
14791
14857
  this.waitForStylesAndFonts(),
14792
14858
  this.waitForFonts()
14793
14859
  ]);
14860
+ const waitStylesTime = performance.now() - waitStylesStartTime;
14861
+ if (!this.options.silentMode) {
14862
+ console.log(` ⏱️ 等待样式和字体加载耗时: ${waitStylesTime.toFixed(2)}ms`);
14863
+ }
14794
14864
  // 等待元素完全渲染(特别是对于 modern-screenshot)
14865
+ const waitRenderStartTime = performance.now();
14795
14866
  await new Promise(resolve => requestAnimationFrame(() => {
14796
14867
  requestAnimationFrame(() => resolve());
14797
14868
  }));
14869
+ const waitRenderTime = performance.now() - waitRenderStartTime;
14870
+ if (!this.options.silentMode) {
14871
+ console.log(` ⏱️ 等待渲染完成耗时: ${waitRenderTime.toFixed(2)}ms`);
14872
+ }
14798
14873
  // 选择截图引擎
14799
14874
  const selectedEngine = this.options.engine || 'modern-screenshot';
14800
14875
  if (!this.options.silentMode) {
@@ -14821,6 +14896,7 @@ class ScreenshotManager {
14821
14896
  }
14822
14897
  let dataUrl;
14823
14898
  // 根据选择的引擎进行截图
14899
+ const engineStartTime = performance.now();
14824
14900
  if (selectedEngine === 'html2canvas') {
14825
14901
  // html2canvas 需要更长的等待时间确保样式加载
14826
14902
  // 额外等待样式和字体加载完成
@@ -14843,6 +14919,10 @@ class ScreenshotManager {
14843
14919
  await new Promise(resolve => setTimeout(resolve, 50));
14844
14920
  dataUrl = await this.takeScreenshotWithModernScreenshot(this.targetElement);
14845
14921
  }
14922
+ const engineTime = performance.now() - engineStartTime;
14923
+ if (!this.options.silentMode) {
14924
+ console.log(` ⏱️ 截图引擎执行耗时: ${engineTime.toFixed(2)}ms`);
14925
+ }
14846
14926
  const timestamp = Date.now();
14847
14927
  // 更新状态
14848
14928
  this.screenshotCount++;
@@ -14876,6 +14956,8 @@ class ScreenshotManager {
14876
14956
  break;
14877
14957
  }
14878
14958
  }
14959
+ // 计算截图总耗时
14960
+ const screenshotTotalTime = performance.now() - screenshotStartTime;
14879
14961
  // 打印基本信息
14880
14962
  const base64Data = dataUrl.split(',')[1] || '';
14881
14963
  if (!this.options.silentMode) {
@@ -14884,6 +14966,7 @@ class ScreenshotManager {
14884
14966
  console.log(`📸 时间: ${new Date(timestamp).toLocaleTimeString()}`);
14885
14967
  console.log(`📸 原始大小: ${Math.round(base64Data.length * 0.75 / 1024)} KB`);
14886
14968
  console.log(`📸 Base64 长度: ${base64Data.length} 字符`);
14969
+ console.log(` ⏱️ 截图总耗时: ${screenshotTotalTime.toFixed(2)}ms`);
14887
14970
  // 完整打印压缩前的 base64
14888
14971
  console.log(`📸 [压缩前] 完整 Base64:`);
14889
14972
  console.log(base64Data);
@@ -14906,12 +14989,16 @@ class ScreenshotManager {
14906
14989
  }
14907
14990
  }
14908
14991
  if (this.worker) {
14992
+ // 记录压缩开始时间
14993
+ const compressStartTime = performance.now();
14909
14994
  if (!this.options.silentMode) {
14910
14995
  console.log('📸 发送到 WebWorker 进行压缩...');
14911
14996
  }
14912
14997
  // 保存原始 dataUrl 用于后续对比(在 Worker 压缩完成后)
14913
14998
  // 注意:此时 screenshotHistory 中已经保存了原始的 dataUrl
14914
14999
  const originalDataUrlForCompression = dataUrl;
15000
+ this.worker.__compressStartTime = compressStartTime;
15001
+ this.worker.__scheduleStartTime = scheduleStartTime;
14915
15002
  this.worker.postMessage({
14916
15003
  type: 'COMPRESS_IMAGE',
14917
15004
  data: {
@@ -17048,6 +17135,11 @@ class ScreenshotManager {
17048
17135
  const { type, data } = e.data;
17049
17136
  if (type === 'SCREENSHOT_RESULT' && data?.compressed) {
17050
17137
  const compressed = data.compressed;
17138
+ const compressEndTime = performance.now();
17139
+ // 获取压缩开始时间
17140
+ const compressStartTime = newWorker.__compressStartTime || compressEndTime;
17141
+ const scheduleStartTime = newWorker.__scheduleStartTime;
17142
+ const compressTime = compressEndTime - compressStartTime;
17051
17143
  // 获取压缩前的原始数据
17052
17144
  // 从 screenshotHistory 中获取(在发送到 Worker 之前已经保存了原始数据)
17053
17145
  const originalDataUrl = this.screenshotHistory.length > 0
@@ -17066,6 +17158,7 @@ class ScreenshotManager {
17066
17158
  // 压缩失败,使用原始数据
17067
17159
  console.warn('📸 [Worker 压缩] ⚠️ 压缩失败,使用原始截图');
17068
17160
  console.warn(` ⚠️ 错误: ${compressed.error}`);
17161
+ console.log(` ⏱️ Worker 压缩耗时: ${compressTime.toFixed(2)}ms`);
17069
17162
  console.log(`📸 [压缩前] Base64 长度: ${originalBase64.length} 字符`);
17070
17163
  console.log(`📸 [压缩前] 完整 Base64:`);
17071
17164
  console.log(originalBase64);
@@ -17081,6 +17174,7 @@ class ScreenshotManager {
17081
17174
  console.log(` 原始大小: ${originalKB} KB`);
17082
17175
  console.log(` 压缩后: ${compressedKB} KB`);
17083
17176
  console.log(` 压缩率: ${ratio}%`);
17177
+ console.log(` ⏱️ Worker 压缩耗时: ${compressTime.toFixed(2)}ms`);
17084
17178
  // 完整打印压缩前后的 base64 对比
17085
17179
  console.log(`📸 [压缩前] Base64 长度: ${originalBase64.length} 字符`);
17086
17180
  console.log(`📸 [压缩前] 完整 Base64:`);
@@ -17096,9 +17190,19 @@ class ScreenshotManager {
17096
17190
  }
17097
17191
  // 压缩完成后(无论成功或失败),如果配置了二进制模式,发送数据到 iframe
17098
17192
  if (this.currentBinaryConfig && compressed.dataUrl) {
17099
- this.sendCompressedScreenshotToIframe(compressed.dataUrl);
17193
+ this.sendCompressedScreenshotToIframe(compressed.dataUrl, scheduleStartTime, compressEndTime);
17100
17194
  }
17101
17195
  }
17196
+ // 任务完成(压缩模式)- 无论是否成功,都要标记为完成
17197
+ this.isCurrentTaskCompleted = true;
17198
+ // 如果还在运行,触发下一次任务
17199
+ if (this.isRunning && this.scheduleNextFn) {
17200
+ const currentInterval = this.dynamicInterval || this.options.interval;
17201
+ this.screenshotTimer = setTimeout(() => {
17202
+ this.screenshotTimer = null;
17203
+ this.scheduleNextFn?.();
17204
+ }, currentInterval);
17205
+ }
17102
17206
  }
17103
17207
  };
17104
17208
  newWorker.onerror = (e) => {
@@ -17128,6 +17232,16 @@ class ScreenshotManager {
17128
17232
  }
17129
17233
  }
17130
17234
  }
17235
+ // Worker 错误时,任务也完成
17236
+ this.isCurrentTaskCompleted = true;
17237
+ // 如果还在运行,触发下一次任务
17238
+ if (this.isRunning && this.scheduleNextFn) {
17239
+ const currentInterval = this.dynamicInterval || this.options.interval;
17240
+ this.screenshotTimer = setTimeout(() => {
17241
+ this.screenshotTimer = null;
17242
+ this.scheduleNextFn?.();
17243
+ }, currentInterval);
17244
+ }
17131
17245
  };
17132
17246
  // 注意:不要立即 revokeObjectURL,因为 Worker 需要这个 URL 保持有效
17133
17247
  // 在 destroy() 方法中清理 Worker 时再 revoke
@@ -17386,10 +17500,11 @@ class ScreenshotManager {
17386
17500
  /**
17387
17501
  * 发送压缩后的截图到 iframe
17388
17502
  */
17389
- sendCompressedScreenshotToIframe(dataUrl) {
17503
+ sendCompressedScreenshotToIframe(dataUrl, scheduleStartTime, compressEndTime) {
17390
17504
  if (!this.currentBinaryConfig || !this.sendToIframeCallback) {
17391
17505
  return;
17392
17506
  }
17507
+ const sendStartTime = performance.now();
17393
17508
  try {
17394
17509
  // 计算 base64 大小
17395
17510
  const base64Data = dataUrl.split(',')[1] || '';
@@ -17404,14 +17519,20 @@ class ScreenshotManager {
17404
17519
  console.log(dataUrl);
17405
17520
  }
17406
17521
  // 将截图转换为 ArrayBuffer
17522
+ const convertStartTime = performance.now();
17407
17523
  const imageBuffer = this.dataUrlToArrayBuffer(dataUrl);
17408
17524
  const imageBufferSize = imageBuffer.byteLength;
17525
+ const convertTime = performance.now() - convertStartTime;
17409
17526
  // 构建配置的二进制结构
17527
+ const buildConfigStartTime = performance.now();
17410
17528
  const configBuffer = this.buildBinaryConfig(this.currentBinaryConfig);
17411
17529
  const configBufferSize = configBuffer.byteLength;
17530
+ const buildConfigTime = performance.now() - buildConfigStartTime;
17412
17531
  // 合并配置字节和图片字节(配置在前)
17532
+ const combineStartTime = performance.now();
17413
17533
  const combinedBuffer = this.combineBinaryData(configBuffer, imageBuffer);
17414
17534
  const combinedBufferSize = combinedBuffer.byteLength;
17535
+ const combineTime = performance.now() - combineStartTime;
17415
17536
  // 打印大小信息
17416
17537
  if (!this.options.silentMode) {
17417
17538
  console.log('📸 [压缩后-大小统计]');
@@ -17419,15 +17540,34 @@ class ScreenshotManager {
17419
17540
  console.log(` 图片字节大小: ${(imageBufferSize / 1024).toFixed(2)} KB (${imageBufferSize} 字节)`);
17420
17541
  console.log(` 配置字节大小: ${configBufferSize} 字节`);
17421
17542
  console.log(` 拼接后总大小: ${(combinedBufferSize / 1024).toFixed(2)} KB (${combinedBufferSize} 字节)`);
17543
+ console.log(` ⏱️ 数据转换耗时: ${convertTime.toFixed(2)}ms`);
17544
+ console.log(` ⏱️ 构建配置耗时: ${buildConfigTime.toFixed(2)}ms`);
17545
+ console.log(` ⏱️ 合并数据耗时: ${combineTime.toFixed(2)}ms`);
17422
17546
  }
17423
17547
  // 发送二进制数据到 iframe
17548
+ const sendCallbackStartTime = performance.now();
17424
17549
  const message = {
17425
17550
  type: 'screenshotBinary',
17426
17551
  data: combinedBuffer
17427
17552
  };
17428
17553
  this.sendToIframeCallback(message);
17554
+ const sendCallbackTime = performance.now() - sendCallbackStartTime;
17555
+ const totalSendTime = performance.now() - sendStartTime;
17556
+ let totalTime = 0;
17557
+ if (scheduleStartTime) {
17558
+ totalTime = performance.now() - scheduleStartTime;
17559
+ }
17429
17560
  if (!this.options.silentMode) {
17430
17561
  console.log('📸 [压缩后] ✅ 二进制数据已发送到 iframe');
17562
+ console.log(` ⏱️ 发送回调耗时: ${sendCallbackTime.toFixed(2)}ms`);
17563
+ console.log(` ⏱️ 发送阶段总耗时: ${totalSendTime.toFixed(2)}ms`);
17564
+ if (scheduleStartTime) {
17565
+ console.log(` ⏱️ 从定时开始到发送完成总耗时: ${totalTime.toFixed(2)}ms`);
17566
+ if (compressEndTime) {
17567
+ const compressToSendTime = sendStartTime - compressEndTime;
17568
+ console.log(` ⏱️ 压缩完成到发送开始耗时: ${compressToSendTime.toFixed(2)}ms`);
17569
+ }
17570
+ }
17431
17571
  }
17432
17572
  }
17433
17573
  catch (error) {