customer-chat-sdk 1.0.33 → 1.0.34
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.
- package/dist/core/ScreenshotManager.d.ts +9 -0
- package/dist/core/ScreenshotManager.d.ts.map +1 -1
- package/dist/customer-sdk.cjs.js +194 -31
- package/dist/customer-sdk.esm.js +194 -31
- package/dist/customer-sdk.min.js +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/customer-sdk.esm.js
CHANGED
|
@@ -6503,13 +6503,17 @@ class ScreenshotManager {
|
|
|
6503
6503
|
this.visibleElementsCache = new Set();
|
|
6504
6504
|
// 预连接状态
|
|
6505
6505
|
this.preconnected = false;
|
|
6506
|
+
// 图片下载队列和并发控制(防止频繁截图时重复下载)
|
|
6507
|
+
this.imageDownloadQueue = new Map(); // URL -> Promise,避免重复下载
|
|
6508
|
+
this.activeDownloads = new Set(); // 正在下载的 URL
|
|
6509
|
+
this.maxConcurrentImageDownloads = 5; // 最大并发下载数(降低,避免内存问题)
|
|
6506
6510
|
// 全局错误处理器
|
|
6507
6511
|
this.globalErrorHandler = null;
|
|
6508
6512
|
this.globalRejectionHandler = null;
|
|
6509
6513
|
this.targetElement = targetElement;
|
|
6510
6514
|
this.options = {
|
|
6511
6515
|
interval: options.interval ?? 5000,
|
|
6512
|
-
quality: options.quality ?? 0.4
|
|
6516
|
+
quality: options.quality ?? 0.3, // 降低默认质量:0.4 -> 0.3,减少 base64 大小
|
|
6513
6517
|
scale: options.scale ?? 1,
|
|
6514
6518
|
maxHistory: options.maxHistory ?? 10,
|
|
6515
6519
|
compress: options.compress ?? false,
|
|
@@ -6533,7 +6537,9 @@ class ScreenshotManager {
|
|
|
6533
6537
|
useIntersectionObserver: options.useIntersectionObserver ?? true, // 默认使用 Intersection Observer
|
|
6534
6538
|
fetchPriority: options.fetchPriority ?? 'high', // 默认高优先级
|
|
6535
6539
|
maxCacheSize: options.maxCacheSize ?? 50, // 默认最大50MB
|
|
6536
|
-
maxCacheAge: options.maxCacheAge ?? 86400000 // 默认24小时(86400000ms)
|
|
6540
|
+
maxCacheAge: options.maxCacheAge ?? 86400000, // 默认24小时(86400000ms)
|
|
6541
|
+
maxImageSize: options.maxImageSize ?? 5, // 不使用代理时,单个图片最大尺寸(MB),默认5MB
|
|
6542
|
+
skipLargeImages: options.skipLargeImages ?? true // 不使用代理时,是否跳过过大的图片,默认true(跳过)
|
|
6537
6543
|
};
|
|
6538
6544
|
this.setupMessageListener();
|
|
6539
6545
|
this.setupVisibilityChangeListener();
|
|
@@ -7090,13 +7096,19 @@ class ScreenshotManager {
|
|
|
7090
7096
|
}
|
|
7091
7097
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
7092
7098
|
const isLowEndDevice = navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4;
|
|
7093
|
-
|
|
7094
|
-
//
|
|
7095
|
-
//
|
|
7099
|
+
// 进一步降低质量以减少 base64 大小
|
|
7100
|
+
// 桌面设备:使用配置的质量(默认 0.3)
|
|
7101
|
+
// 移动设备/低端设备:进一步降低到 0.2(最低)
|
|
7102
|
+
const finalQuality = isMobile || isLowEndDevice
|
|
7103
|
+
? Math.max(this.options.quality * 0.65, 0.2) // 移动设备:质量 * 0.65,最低 0.2
|
|
7104
|
+
: this.options.quality; // 桌面设备:使用配置的质量(默认 0.3)
|
|
7105
|
+
// 计算压缩后的尺寸(对所有元素都应用,包括 document.body)
|
|
7106
|
+
// 这样可以避免生成过大的截图,减少 base64 大小
|
|
7096
7107
|
const { width, height } = this.calculateCompressedSize(elementWidth, elementHeight, this.options.maxWidth, this.options.maxHeight);
|
|
7097
|
-
//
|
|
7098
|
-
|
|
7099
|
-
const
|
|
7108
|
+
// 对于所有元素都应用尺寸限制(包括 body),避免截图过大
|
|
7109
|
+
// 如果计算后的尺寸小于元素实际尺寸,使用压缩尺寸;否则使用元素实际尺寸(但不超过最大值)
|
|
7110
|
+
const finalWidth = width < elementWidth ? width : Math.min(elementWidth, this.options.maxWidth);
|
|
7111
|
+
const finalHeight = height < elementHeight ? height : Math.min(elementHeight, this.options.maxHeight);
|
|
7100
7112
|
// 处理跨域图片的函数
|
|
7101
7113
|
const handleCrossOriginImage = async (url) => {
|
|
7102
7114
|
// 如果是 data URL 或 blob URL,直接返回
|
|
@@ -7195,9 +7207,64 @@ class ScreenshotManager {
|
|
|
7195
7207
|
return url;
|
|
7196
7208
|
}
|
|
7197
7209
|
}
|
|
7198
|
-
//
|
|
7210
|
+
// 如果没有配置代理,需要添加内存保护机制
|
|
7211
|
+
// 不使用代理时,modern-screenshot 会直接下载图片,可能导致内存问题
|
|
7212
|
+
// 由于已配置 CORS,可以直接下载并检查大小
|
|
7199
7213
|
if (this.options.enableCORS) {
|
|
7200
|
-
|
|
7214
|
+
// 对于不使用代理的情况,添加内存保护和缓存机制:
|
|
7215
|
+
// 1. 先检查内存缓存(避免重复下载)
|
|
7216
|
+
// 2. 检查 IndexedDB 缓存
|
|
7217
|
+
// 3. 使用下载队列避免并发重复下载
|
|
7218
|
+
// 4. 下载时检查大小,如果过大则使用占位符
|
|
7219
|
+
// 先检查内存缓存(优先使用缓存,避免重复下载)
|
|
7220
|
+
const cachedDataUrl = this.getCachedImage(url);
|
|
7221
|
+
if (cachedDataUrl) {
|
|
7222
|
+
if (!this.options.silentMode) {
|
|
7223
|
+
console.log(`📸 ✅ 使用内存缓存图片(无代理模式): ${url.substring(0, 50)}...`);
|
|
7224
|
+
}
|
|
7225
|
+
return cachedDataUrl;
|
|
7226
|
+
}
|
|
7227
|
+
// 检查 IndexedDB 缓存(如果启用)
|
|
7228
|
+
if (this.options.useIndexedDB) {
|
|
7229
|
+
const indexedDBCache = await this.getIndexedDBCache(url);
|
|
7230
|
+
if (indexedDBCache) {
|
|
7231
|
+
// 同步到内存缓存
|
|
7232
|
+
this.setCachedImage(url, indexedDBCache);
|
|
7233
|
+
if (!this.options.silentMode) {
|
|
7234
|
+
console.log(`📸 ✅ 使用 IndexedDB 缓存图片(无代理模式): ${url.substring(0, 50)}...`);
|
|
7235
|
+
}
|
|
7236
|
+
return indexedDBCache;
|
|
7237
|
+
}
|
|
7238
|
+
}
|
|
7239
|
+
// 检查是否正在下载(避免重复下载)
|
|
7240
|
+
if (this.imageDownloadQueue.has(url)) {
|
|
7241
|
+
// 如果已经在下载队列中,等待现有下载完成
|
|
7242
|
+
if (!this.options.silentMode) {
|
|
7243
|
+
console.log(`📸 ⏳ 等待图片下载完成: ${url.substring(0, 50)}...`);
|
|
7244
|
+
}
|
|
7245
|
+
return await this.imageDownloadQueue.get(url);
|
|
7246
|
+
}
|
|
7247
|
+
// 检查并发下载数限制
|
|
7248
|
+
if (this.activeDownloads.size >= this.maxConcurrentImageDownloads) {
|
|
7249
|
+
// 并发数已满,返回原 URL,让 modern-screenshot 自己处理(可能会失败,但不阻塞)
|
|
7250
|
+
if (!this.options.silentMode) {
|
|
7251
|
+
console.warn(`📸 ⚠️ 并发下载数已满(${this.activeDownloads.size}/${this.maxConcurrentImageDownloads}),跳过: ${url.substring(0, 50)}...`);
|
|
7252
|
+
}
|
|
7253
|
+
return url;
|
|
7254
|
+
}
|
|
7255
|
+
// 创建下载 Promise 并加入队列
|
|
7256
|
+
const downloadPromise = this.downloadImageWithoutProxy(url);
|
|
7257
|
+
this.imageDownloadQueue.set(url, downloadPromise);
|
|
7258
|
+
this.activeDownloads.add(url);
|
|
7259
|
+
try {
|
|
7260
|
+
const result = await downloadPromise;
|
|
7261
|
+
return result;
|
|
7262
|
+
}
|
|
7263
|
+
finally {
|
|
7264
|
+
// 下载完成后清理
|
|
7265
|
+
this.imageDownloadQueue.delete(url);
|
|
7266
|
+
this.activeDownloads.delete(url);
|
|
7267
|
+
}
|
|
7201
7268
|
}
|
|
7202
7269
|
// 默认返回原 URL
|
|
7203
7270
|
return url;
|
|
@@ -7218,11 +7285,14 @@ class ScreenshotManager {
|
|
|
7218
7285
|
}
|
|
7219
7286
|
this.screenshotContext = null;
|
|
7220
7287
|
}
|
|
7288
|
+
// Worker 数量配置:移动设备/低端设备使用 1 个 Worker,桌面设备使用 2 个
|
|
7289
|
+
// workerNumber > 0 会启用 Worker 模式,截图处理在后台线程执行,不会阻塞主线程 UI
|
|
7221
7290
|
const workerNumber = isMobile || isLowEndDevice ? 1 : 2;
|
|
7222
7291
|
// 构建 createContext 配置
|
|
7292
|
+
// 参考: https://github.com/qq15725/modern-screenshot/blob/main/src/options.ts
|
|
7223
7293
|
const contextOptions = {
|
|
7224
|
-
workerNumber,
|
|
7225
|
-
quality:
|
|
7294
|
+
workerNumber, // Worker 数量,> 0 启用 Worker 模式
|
|
7295
|
+
quality: finalQuality, // 图片质量(0-1),已优化为更低的值以减少 base64 大小
|
|
7226
7296
|
fetchFn: handleCrossOriginImage, // 使用代理服务器处理跨域图片
|
|
7227
7297
|
fetch: {
|
|
7228
7298
|
requestInit: {
|
|
@@ -7230,35 +7300,45 @@ class ScreenshotManager {
|
|
|
7230
7300
|
},
|
|
7231
7301
|
bypassingCache: true,
|
|
7232
7302
|
},
|
|
7303
|
+
// 设置最大 canvas 尺寸,防止生成过大的 canvas(避免内存问题)
|
|
7304
|
+
// 参考: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas#maximum_canvas_size
|
|
7305
|
+
// 大多数浏览器限制为 16,777,216 像素(4096x4096),这里设置为更保守的值
|
|
7306
|
+
maximumCanvasSize: 16777216, // 16M 像素(约 4096x4096)
|
|
7233
7307
|
};
|
|
7234
|
-
//
|
|
7235
|
-
//
|
|
7236
|
-
if (
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
if (
|
|
7241
|
-
console.log(`📸
|
|
7308
|
+
// 对所有元素都设置尺寸限制(包括 document.body),避免截图过大
|
|
7309
|
+
// 这样可以减少 base64 大小,提高性能
|
|
7310
|
+
if (finalWidth && finalHeight) {
|
|
7311
|
+
contextOptions.width = finalWidth;
|
|
7312
|
+
contextOptions.height = finalHeight;
|
|
7313
|
+
if (!this.options.silentMode) {
|
|
7314
|
+
if (element === document.body || element === document.documentElement) {
|
|
7315
|
+
console.log(`📸 截取完整页面(document.body),使用压缩尺寸: ${finalWidth}x${finalHeight}`);
|
|
7242
7316
|
}
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
if (!this.options.silentMode) {
|
|
7246
|
-
console.log(`📸 使用元素实际尺寸: ${elementWidth}x${elementHeight}`);
|
|
7317
|
+
else {
|
|
7318
|
+
console.log(`📸 使用压缩尺寸: ${finalWidth}x${finalHeight}`);
|
|
7247
7319
|
}
|
|
7248
7320
|
}
|
|
7249
7321
|
}
|
|
7250
7322
|
else {
|
|
7251
|
-
// 对于 body,不设置尺寸限制,让 modern-screenshot 自动截取完整页面
|
|
7252
7323
|
if (!this.options.silentMode) {
|
|
7253
|
-
console.log(`📸
|
|
7324
|
+
console.log(`📸 使用元素实际尺寸: ${elementWidth}x${elementHeight}`);
|
|
7254
7325
|
}
|
|
7255
7326
|
}
|
|
7256
|
-
//
|
|
7327
|
+
// 缩放配置:移动设备使用更低的缩放比例,进一步减少图片大小
|
|
7328
|
+
// scale < 1 会降低图片分辨率,减少 base64 大小
|
|
7257
7329
|
if (this.options.scale !== 1) {
|
|
7258
|
-
contextOptions.scale = isMobile ? 0.
|
|
7330
|
+
contextOptions.scale = isMobile ? 0.7 : this.options.scale; // 移动设备:0.8 -> 0.7
|
|
7259
7331
|
}
|
|
7260
|
-
|
|
7332
|
+
else if (isMobile) {
|
|
7333
|
+
// 如果未指定 scale,移动设备默认使用 0.7
|
|
7334
|
+
contextOptions.scale = 0.7;
|
|
7335
|
+
}
|
|
7336
|
+
// modern-screenshot 会自动处理 worker URL,不需要手动设置 workerUrl
|
|
7337
|
+
// 当 workerNumber > 0 时,截图处理会在 Worker 线程中执行,不会阻塞主线程 UI
|
|
7261
7338
|
// 创建 Worker 上下文(每次截图都创建新的,确保元素状态最新)
|
|
7339
|
+
if (!this.options.silentMode) {
|
|
7340
|
+
console.log(`📸 Worker 模式: ${workerNumber} 个 Worker,质量: ${finalQuality.toFixed(2)},缩放: ${contextOptions.scale || 1}`);
|
|
7341
|
+
}
|
|
7262
7342
|
this.screenshotContext = await createContext$1(element, contextOptions);
|
|
7263
7343
|
try {
|
|
7264
7344
|
// 使用 Worker 上下文进行截图
|
|
@@ -7287,7 +7367,8 @@ class ScreenshotManager {
|
|
|
7287
7367
|
img.src = dataUrl;
|
|
7288
7368
|
});
|
|
7289
7369
|
let mimeType = 'image/jpeg';
|
|
7290
|
-
|
|
7370
|
+
// 使用与 createContext 相同的质量设置
|
|
7371
|
+
let conversionQuality = finalQuality;
|
|
7291
7372
|
if (this.options.outputFormat === 'webp' && !isMobile) {
|
|
7292
7373
|
try {
|
|
7293
7374
|
const testCanvas = document.createElement('canvas');
|
|
@@ -7302,9 +7383,10 @@ class ScreenshotManager {
|
|
|
7302
7383
|
mimeType = 'image/jpeg';
|
|
7303
7384
|
}
|
|
7304
7385
|
}
|
|
7386
|
+
// 使用优化后的质量进行格式转换
|
|
7305
7387
|
const convertedDataUrl = mimeType === 'image/png'
|
|
7306
7388
|
? canvas.toDataURL(mimeType)
|
|
7307
|
-
: canvas.toDataURL(mimeType,
|
|
7389
|
+
: canvas.toDataURL(mimeType, conversionQuality);
|
|
7308
7390
|
return convertedDataUrl;
|
|
7309
7391
|
}
|
|
7310
7392
|
return dataUrl;
|
|
@@ -7780,6 +7862,87 @@ class ScreenshotManager {
|
|
|
7780
7862
|
}
|
|
7781
7863
|
return dataUrl;
|
|
7782
7864
|
}
|
|
7865
|
+
/**
|
|
7866
|
+
* 不使用代理时下载图片(带内存保护)
|
|
7867
|
+
*/
|
|
7868
|
+
async downloadImageWithoutProxy(url) {
|
|
7869
|
+
try {
|
|
7870
|
+
// 直接下载图片并检查大小
|
|
7871
|
+
const controller = new AbortController();
|
|
7872
|
+
const timeoutId = setTimeout(() => controller.abort(), this.options.imageLoadTimeout || 5000);
|
|
7873
|
+
const response = await fetch(url, {
|
|
7874
|
+
method: 'GET',
|
|
7875
|
+
mode: 'cors',
|
|
7876
|
+
credentials: 'omit',
|
|
7877
|
+
cache: 'no-cache',
|
|
7878
|
+
signal: controller.signal
|
|
7879
|
+
}).catch(() => null).finally(() => {
|
|
7880
|
+
clearTimeout(timeoutId);
|
|
7881
|
+
});
|
|
7882
|
+
if (response && response.ok) {
|
|
7883
|
+
// 检查 Content-Length(如果可用)
|
|
7884
|
+
const contentLength = response.headers.get('content-length');
|
|
7885
|
+
if (contentLength) {
|
|
7886
|
+
const sizeMB = parseInt(contentLength, 10) / (1024 * 1024);
|
|
7887
|
+
const maxSizeMB = this.options.maxImageSize || 5;
|
|
7888
|
+
if (sizeMB > maxSizeMB) {
|
|
7889
|
+
if (this.options.skipLargeImages) {
|
|
7890
|
+
// 跳过过大的图片,返回占位符
|
|
7891
|
+
if (!this.options.silentMode) {
|
|
7892
|
+
console.warn(`📸 ⚠️ 跳过过大图片(${sizeMB.toFixed(2)}MB > ${maxSizeMB}MB): ${url.substring(0, 100)}...`);
|
|
7893
|
+
}
|
|
7894
|
+
// 返回一个 1x1 的透明占位符,避免截图失败
|
|
7895
|
+
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
|
7896
|
+
}
|
|
7897
|
+
else {
|
|
7898
|
+
// 不跳过,但添加警告
|
|
7899
|
+
if (!this.options.silentMode) {
|
|
7900
|
+
console.warn(`📸 ⚠️ 图片较大(${sizeMB.toFixed(2)}MB),可能导致内存问题: ${url.substring(0, 100)}...`);
|
|
7901
|
+
}
|
|
7902
|
+
}
|
|
7903
|
+
}
|
|
7904
|
+
}
|
|
7905
|
+
// 如果大小检查通过或无法获取大小,下载图片并转换为 data URL
|
|
7906
|
+
// 但为了进一步保护内存,在下载 blob 后也检查实际大小
|
|
7907
|
+
const blob = await response.blob();
|
|
7908
|
+
const blobSizeMB = blob.size / (1024 * 1024);
|
|
7909
|
+
const maxSizeMB = this.options.maxImageSize || 5;
|
|
7910
|
+
if (blobSizeMB > maxSizeMB) {
|
|
7911
|
+
if (this.options.skipLargeImages) {
|
|
7912
|
+
// 跳过过大的图片,返回占位符
|
|
7913
|
+
if (!this.options.silentMode) {
|
|
7914
|
+
console.warn(`📸 ⚠️ 跳过过大图片(实际大小 ${blobSizeMB.toFixed(2)}MB > ${maxSizeMB}MB): ${url.substring(0, 100)}...`);
|
|
7915
|
+
}
|
|
7916
|
+
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
|
7917
|
+
}
|
|
7918
|
+
else {
|
|
7919
|
+
// 不跳过,但添加警告
|
|
7920
|
+
if (!this.options.silentMode) {
|
|
7921
|
+
console.warn(`📸 ⚠️ 图片较大(实际大小 ${blobSizeMB.toFixed(2)}MB),可能导致内存问题: ${url.substring(0, 100)}...`);
|
|
7922
|
+
}
|
|
7923
|
+
}
|
|
7924
|
+
}
|
|
7925
|
+
// 转换为 data URL
|
|
7926
|
+
const dataUrl = await this.blobToDataUrl(blob);
|
|
7927
|
+
// 缓存结果(带时间戳,10分钟有效)
|
|
7928
|
+
this.setCachedImage(url, dataUrl);
|
|
7929
|
+
// 如果启用 IndexedDB,也保存到 IndexedDB
|
|
7930
|
+
if (this.options.useIndexedDB) {
|
|
7931
|
+
await this.setIndexedDBCache(url, dataUrl);
|
|
7932
|
+
}
|
|
7933
|
+
return dataUrl;
|
|
7934
|
+
}
|
|
7935
|
+
// 如果下载失败,返回原 URL,让 modern-screenshot 自己处理
|
|
7936
|
+
return url;
|
|
7937
|
+
}
|
|
7938
|
+
catch (error) {
|
|
7939
|
+
// 下载失败,返回原 URL,让 modern-screenshot 自己处理
|
|
7940
|
+
if (!this.options.silentMode) {
|
|
7941
|
+
console.warn(`📸 ⚠️ 下载图片失败: ${url.substring(0, 100)}...`, error);
|
|
7942
|
+
}
|
|
7943
|
+
return url;
|
|
7944
|
+
}
|
|
7945
|
+
}
|
|
7783
7946
|
/**
|
|
7784
7947
|
* 将 blob 转换为 data URL
|
|
7785
7948
|
*/
|