customer-chat-sdk 1.0.61 → 1.0.63
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 +191 -20
- package/dist/customer-sdk.esm.js +191 -21
- package/dist/customer-sdk.min.js +2 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/customer-sdk.esm.js
CHANGED
|
@@ -14988,6 +14988,10 @@ class ScreenshotManager {
|
|
|
14988
14988
|
if (!this.options.silentMode) {
|
|
14989
14989
|
console.log('📸 使用 snapdom 引擎截图...');
|
|
14990
14990
|
}
|
|
14991
|
+
// 限制只截图可见区域(viewport),避免截图不可见区域
|
|
14992
|
+
// 注意:snapdom 可能不支持直接限制可见区域,所以这里只是添加警告
|
|
14993
|
+
let targetElement = element;
|
|
14994
|
+
let tempContainer = null;
|
|
14991
14995
|
try {
|
|
14992
14996
|
// 检查元素是否存在和可见
|
|
14993
14997
|
const rect = element.getBoundingClientRect();
|
|
@@ -14996,6 +15000,53 @@ class ScreenshotManager {
|
|
|
14996
15000
|
}
|
|
14997
15001
|
// 构建 snapdom 选项
|
|
14998
15002
|
const options = {};
|
|
15003
|
+
if (element === document.body || element === document.documentElement) {
|
|
15004
|
+
// 对于 body 或 html 元素,创建一个临时容器,只包含可见区域
|
|
15005
|
+
// 使用 html2canvas 的方式:创建一个固定大小的容器,只包含可见内容
|
|
15006
|
+
tempContainer = document.createElement('div');
|
|
15007
|
+
tempContainer.style.position = 'fixed';
|
|
15008
|
+
tempContainer.style.top = '0';
|
|
15009
|
+
tempContainer.style.left = '0';
|
|
15010
|
+
tempContainer.style.width = `${window.innerWidth}px`;
|
|
15011
|
+
tempContainer.style.height = `${window.innerHeight}px`;
|
|
15012
|
+
tempContainer.style.overflow = 'hidden';
|
|
15013
|
+
tempContainer.style.zIndex = '999999';
|
|
15014
|
+
tempContainer.style.pointerEvents = 'none';
|
|
15015
|
+
tempContainer.style.backgroundColor = window.getComputedStyle(document.body).backgroundColor || 'white';
|
|
15016
|
+
// 创建一个包装器,只包含可见区域的内容
|
|
15017
|
+
// 注意:这里不克隆整个 body,而是直接使用 body 作为父元素
|
|
15018
|
+
// 但限制容器的尺寸为可见区域
|
|
15019
|
+
document.body.appendChild(tempContainer);
|
|
15020
|
+
// 使用 body 作为目标,但通过容器限制范围
|
|
15021
|
+
// 实际上,我们需要直接截图 body,但限制尺寸
|
|
15022
|
+
// 由于 snapdom 可能不支持直接限制尺寸,我们使用一个更简单的方法:
|
|
15023
|
+
// 直接截图 body,但通过 CSS 限制其显示范围(但这会影响页面)
|
|
15024
|
+
// 更好的方法是:使用 html2canvas 的方式,创建一个 canvas 并只绘制可见区域
|
|
15025
|
+
// 简化方案:直接使用 body,但添加尺寸限制选项(如果 snapdom 支持)
|
|
15026
|
+
// 如果不支持,则只能截图整个 body
|
|
15027
|
+
targetElement = element;
|
|
15028
|
+
if (!this.options.silentMode) {
|
|
15029
|
+
console.log(`📸 注意:snapdom 将截图整个 body,建议使用 html2canvas 或 modern-screenshot 来限制可见区域`);
|
|
15030
|
+
console.log(`📸 可见区域尺寸: ${window.innerWidth}x${window.innerHeight}`);
|
|
15031
|
+
}
|
|
15032
|
+
// 清理临时容器(未使用)
|
|
15033
|
+
if (tempContainer && tempContainer.parentNode) {
|
|
15034
|
+
tempContainer.parentNode.removeChild(tempContainer);
|
|
15035
|
+
}
|
|
15036
|
+
tempContainer = null;
|
|
15037
|
+
}
|
|
15038
|
+
else {
|
|
15039
|
+
// 对于其他元素,检查是否完全可见
|
|
15040
|
+
const elementRect = element.getBoundingClientRect();
|
|
15041
|
+
const isFullyVisible = elementRect.top >= 0 &&
|
|
15042
|
+
elementRect.left >= 0 &&
|
|
15043
|
+
elementRect.bottom <= window.innerHeight &&
|
|
15044
|
+
elementRect.right <= window.innerWidth;
|
|
15045
|
+
if (!isFullyVisible && !this.options.silentMode) {
|
|
15046
|
+
console.warn(`📸 ⚠️ 元素部分不可见,snapdom 可能会截图整个元素(包括不可见部分)`);
|
|
15047
|
+
console.warn(`📸 建议:使用 html2canvas 或 modern-screenshot 来限制可见区域`);
|
|
15048
|
+
}
|
|
15049
|
+
}
|
|
14999
15050
|
// 如果配置了代理服务器,使用 useProxy 选项处理跨域图片和字体
|
|
15000
15051
|
// 参考: https://github.com/zumerlab/snapdom/blob/main/README_CN.md#跨域图片和字体-useproxy
|
|
15001
15052
|
// 只有当 useProxy 为 true 且 proxyUrl 存在时才使用代理
|
|
@@ -15030,16 +15081,16 @@ class ScreenshotManager {
|
|
|
15030
15081
|
switch (outputFormat) {
|
|
15031
15082
|
case 'jpeg':
|
|
15032
15083
|
// 使用 toJpg 快捷方法(snapdom 使用 jpg 作为方法名)
|
|
15033
|
-
img = await snapdom.toJpg(
|
|
15084
|
+
img = await snapdom.toJpg(targetElement, options);
|
|
15034
15085
|
break;
|
|
15035
15086
|
case 'webp':
|
|
15036
15087
|
// 使用 toWebp 快捷方法
|
|
15037
|
-
img = await snapdom.toWebp(
|
|
15088
|
+
img = await snapdom.toWebp(targetElement, options);
|
|
15038
15089
|
break;
|
|
15039
15090
|
case 'png':
|
|
15040
15091
|
default:
|
|
15041
15092
|
// 使用 toPng 快捷方法(默认)
|
|
15042
|
-
img = await snapdom.toPng(
|
|
15093
|
+
img = await snapdom.toPng(targetElement, options);
|
|
15043
15094
|
break;
|
|
15044
15095
|
}
|
|
15045
15096
|
// 获取 base64 数据(HTMLImageElement 的 src 属性包含 data URL)
|
|
@@ -15048,12 +15099,28 @@ class ScreenshotManager {
|
|
|
15048
15099
|
if (!dataUrl || dataUrl.length < 100) {
|
|
15049
15100
|
throw new Error('生成的 base64 数据无效或过短');
|
|
15050
15101
|
}
|
|
15102
|
+
// 清理临时容器(如果存在)
|
|
15103
|
+
if (tempContainer !== null) {
|
|
15104
|
+
const container = tempContainer;
|
|
15105
|
+
const parent = container.parentNode;
|
|
15106
|
+
if (parent) {
|
|
15107
|
+
parent.removeChild(container);
|
|
15108
|
+
}
|
|
15109
|
+
}
|
|
15051
15110
|
if (!this.options.silentMode) {
|
|
15052
15111
|
console.log(`📸 snapdom 截图成功!格式: ${outputFormat}, 尺寸: ${img.width}x${img.height}`);
|
|
15053
15112
|
}
|
|
15054
15113
|
return dataUrl;
|
|
15055
15114
|
}
|
|
15056
15115
|
catch (error) {
|
|
15116
|
+
// 确保清理临时容器(即使出错)
|
|
15117
|
+
if (tempContainer !== null) {
|
|
15118
|
+
const container = tempContainer;
|
|
15119
|
+
const parent = container.parentNode;
|
|
15120
|
+
if (parent) {
|
|
15121
|
+
parent.removeChild(container);
|
|
15122
|
+
}
|
|
15123
|
+
}
|
|
15057
15124
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
15058
15125
|
const errorName = error instanceof Error ? error.name : 'Unknown';
|
|
15059
15126
|
// 针对不同类型的错误给出具体提示
|
|
@@ -15450,19 +15517,21 @@ class ScreenshotManager {
|
|
|
15450
15517
|
console.log('📸 使用 modern-screenshot 引擎截图(Worker 模式)...');
|
|
15451
15518
|
}
|
|
15452
15519
|
try {
|
|
15453
|
-
//
|
|
15454
|
-
// 对于 document.body
|
|
15520
|
+
// 获取元素的实际尺寸
|
|
15521
|
+
// 对于 document.body,使用窗口可见区域尺寸(而不是完整滚动内容),避免截图过大
|
|
15455
15522
|
let elementWidth;
|
|
15456
15523
|
let elementHeight;
|
|
15457
15524
|
if (element === document.body || element === document.documentElement) {
|
|
15458
|
-
// 对于 body 或 html
|
|
15459
|
-
|
|
15460
|
-
|
|
15525
|
+
// 对于 body 或 html 元素,使用窗口可见区域尺寸(viewport)
|
|
15526
|
+
// 这样可以避免截图整个页面(包括不可见区域),减少图片大小
|
|
15527
|
+
elementWidth = window.innerWidth;
|
|
15528
|
+
elementHeight = window.innerHeight;
|
|
15461
15529
|
}
|
|
15462
15530
|
else {
|
|
15463
|
-
//
|
|
15464
|
-
|
|
15465
|
-
|
|
15531
|
+
// 对于其他元素,使用元素的可见尺寸(clientWidth/clientHeight)
|
|
15532
|
+
// 如果元素有滚动,只截图可见部分
|
|
15533
|
+
elementWidth = element.clientWidth || element.offsetWidth || element.scrollWidth;
|
|
15534
|
+
elementHeight = element.clientHeight || element.offsetHeight || element.scrollHeight;
|
|
15466
15535
|
}
|
|
15467
15536
|
if (!this.options.silentMode) {
|
|
15468
15537
|
console.log(`📸 目标元素: ${element.tagName}${element.id ? '#' + element.id : ''}${element.className ? '.' + element.className.split(' ').join('.') : ''}`);
|
|
@@ -15486,10 +15555,10 @@ class ScreenshotManager {
|
|
|
15486
15555
|
// 计算压缩后的尺寸(对所有元素都应用,包括 document.body)
|
|
15487
15556
|
// 这样可以避免生成过大的截图,减少 base64 大小
|
|
15488
15557
|
const { width, height } = this.calculateCompressedSize(elementWidth, elementHeight, this.options.maxWidth, this.options.maxHeight);
|
|
15489
|
-
//
|
|
15490
|
-
//
|
|
15491
|
-
const finalWidth = width
|
|
15492
|
-
const finalHeight = height
|
|
15558
|
+
// 使用计算后的压缩尺寸(确保不超过 maxWidth/maxHeight)
|
|
15559
|
+
// 对于 body 元素,已经使用了 window.innerWidth/innerHeight,所以直接使用压缩尺寸即可
|
|
15560
|
+
const finalWidth = width;
|
|
15561
|
+
const finalHeight = height;
|
|
15493
15562
|
// 处理跨域图片的函数
|
|
15494
15563
|
const handleCrossOriginImage = async (url) => {
|
|
15495
15564
|
// 如果是 data URL 或 blob URL,直接返回
|
|
@@ -17114,6 +17183,18 @@ class ScreenshotManager {
|
|
|
17114
17183
|
}
|
|
17115
17184
|
return u8arr.buffer;
|
|
17116
17185
|
}
|
|
17186
|
+
/**
|
|
17187
|
+
* 将 ArrayBuffer 转换为 base64 字符串
|
|
17188
|
+
*/
|
|
17189
|
+
arrayBufferToBase64(buffer) {
|
|
17190
|
+
const bytes = new Uint8Array(buffer);
|
|
17191
|
+
// 使用循环构建字符串,避免 String.fromCharCode.apply 的参数数量限制
|
|
17192
|
+
let binaryStr = '';
|
|
17193
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
17194
|
+
binaryStr += String.fromCharCode(bytes[i]);
|
|
17195
|
+
}
|
|
17196
|
+
return btoa(binaryStr);
|
|
17197
|
+
}
|
|
17117
17198
|
/**
|
|
17118
17199
|
* 构建二进制结构(按顺序:sign[14], type[1], topicLength[1], topic[topicLength], routingKey[8])
|
|
17119
17200
|
* 20 + 1 + 8 + 8 = 37 字节
|
|
@@ -17142,11 +17223,8 @@ class ScreenshotManager {
|
|
|
17142
17223
|
const topicArray = new Uint8Array(buffer, offset, topicLength);
|
|
17143
17224
|
topicArray.set(topicBytes);
|
|
17144
17225
|
offset += topicLength;
|
|
17145
|
-
//
|
|
17146
|
-
|
|
17147
|
-
offset += 1;
|
|
17148
|
-
// routingKey: 8字节
|
|
17149
|
-
const routingKeyBytes = encoder.encode(config.routingKey);
|
|
17226
|
+
// routingKey:
|
|
17227
|
+
const routingKeyBytes = encoder.encode(config.routingKey.trim());
|
|
17150
17228
|
const routingKeyLength = routingKeyBytes.length;
|
|
17151
17229
|
// routingKeyLength: 1字节
|
|
17152
17230
|
view.setUint8(offset, routingKeyLength);
|
|
@@ -17201,6 +17279,16 @@ class ScreenshotManager {
|
|
|
17201
17279
|
// 将截图转换为 ArrayBuffer
|
|
17202
17280
|
const imageBuffer = this.dataUrlToArrayBuffer(latestScreenshot);
|
|
17203
17281
|
const imageBufferSize = imageBuffer.byteLength;
|
|
17282
|
+
// 将 imageBuffer 转换为 base64 字符串(用于和接收端对比)
|
|
17283
|
+
const imageBufferBase64 = this.arrayBufferToBase64(imageBuffer);
|
|
17284
|
+
// 打印 imageBuffer 的 base64 编码(用于和接收端对比)
|
|
17285
|
+
if (!this.options.silentMode) {
|
|
17286
|
+
console.log('📸 [发送前] imageBuffer 转换为 Base64(用于和接收端对比):');
|
|
17287
|
+
console.log(` Base64 长度: ${imageBufferBase64.length} 字符`);
|
|
17288
|
+
console.log(` Base64 完整字符串:`);
|
|
17289
|
+
console.log(imageBufferBase64);
|
|
17290
|
+
console.log(` Data URL: data:image/webp;base64,${imageBufferBase64}`);
|
|
17291
|
+
}
|
|
17204
17292
|
// 构建配置的二进制结构
|
|
17205
17293
|
const configBuffer = this.buildBinaryConfig(config);
|
|
17206
17294
|
const configBufferSize = configBuffer.byteLength;
|
|
@@ -17368,6 +17456,71 @@ class ScreenshotManager {
|
|
|
17368
17456
|
this.stopScreenshot();
|
|
17369
17457
|
}
|
|
17370
17458
|
}
|
|
17459
|
+
/**
|
|
17460
|
+
* 更新截图配置(动态更新,无需重新初始化)
|
|
17461
|
+
* @param newOptions 要更新的配置项(部分更新)
|
|
17462
|
+
*/
|
|
17463
|
+
updateOptions(newOptions) {
|
|
17464
|
+
const oldEngine = this.options.engine;
|
|
17465
|
+
const newEngine = newOptions.engine;
|
|
17466
|
+
// 更新配置
|
|
17467
|
+
if (newOptions.engine !== undefined) {
|
|
17468
|
+
this.options.engine = newOptions.engine;
|
|
17469
|
+
}
|
|
17470
|
+
if (newOptions.quality !== undefined) {
|
|
17471
|
+
this.options.quality = newOptions.quality;
|
|
17472
|
+
}
|
|
17473
|
+
if (newOptions.scale !== undefined) {
|
|
17474
|
+
this.options.scale = newOptions.scale;
|
|
17475
|
+
}
|
|
17476
|
+
if (newOptions.maxWidth !== undefined) {
|
|
17477
|
+
this.options.maxWidth = newOptions.maxWidth;
|
|
17478
|
+
}
|
|
17479
|
+
if (newOptions.maxHeight !== undefined) {
|
|
17480
|
+
this.options.maxHeight = newOptions.maxHeight;
|
|
17481
|
+
}
|
|
17482
|
+
if (newOptions.outputFormat !== undefined) {
|
|
17483
|
+
this.options.outputFormat = newOptions.outputFormat;
|
|
17484
|
+
}
|
|
17485
|
+
if (newOptions.compress !== undefined) {
|
|
17486
|
+
this.options.compress = newOptions.compress;
|
|
17487
|
+
}
|
|
17488
|
+
if (newOptions.silentMode !== undefined) {
|
|
17489
|
+
this.options.silentMode = newOptions.silentMode;
|
|
17490
|
+
}
|
|
17491
|
+
if (newOptions.proxyUrl !== undefined) {
|
|
17492
|
+
this.options.proxyUrl = newOptions.proxyUrl;
|
|
17493
|
+
}
|
|
17494
|
+
if (newOptions.useProxy !== undefined) {
|
|
17495
|
+
this.options.useProxy = newOptions.useProxy;
|
|
17496
|
+
}
|
|
17497
|
+
// 如果引擎改变了,需要清理 context(下次截图时会使用新引擎)
|
|
17498
|
+
if (newEngine && newEngine !== oldEngine) {
|
|
17499
|
+
if (this.screenshotContext) {
|
|
17500
|
+
try {
|
|
17501
|
+
destroyContext(this.screenshotContext);
|
|
17502
|
+
if (!this.options.silentMode) {
|
|
17503
|
+
console.log(`📸 引擎已从 ${oldEngine} 切换到 ${newEngine},已清理旧 context`);
|
|
17504
|
+
}
|
|
17505
|
+
}
|
|
17506
|
+
catch (e) {
|
|
17507
|
+
// 忽略清理错误
|
|
17508
|
+
}
|
|
17509
|
+
this.screenshotContext = null;
|
|
17510
|
+
this.contextElement = null;
|
|
17511
|
+
this.contextOptionsHash = '';
|
|
17512
|
+
this.contextContentHash = '';
|
|
17513
|
+
this.contextLastUpdateTime = 0;
|
|
17514
|
+
}
|
|
17515
|
+
if (!this.options.silentMode) {
|
|
17516
|
+
console.log(`📸 截图引擎已更新: ${oldEngine} → ${newEngine}`);
|
|
17517
|
+
console.log(`📸 下次截图时将使用新引擎: ${newEngine}`);
|
|
17518
|
+
}
|
|
17519
|
+
}
|
|
17520
|
+
else if (!this.options.silentMode) {
|
|
17521
|
+
console.log('📸 截图配置已更新');
|
|
17522
|
+
}
|
|
17523
|
+
}
|
|
17371
17524
|
/**
|
|
17372
17525
|
* 清理资源
|
|
17373
17526
|
*/
|
|
@@ -21008,6 +21161,18 @@ class CustomerServiceSDK {
|
|
|
21008
21161
|
getScreenshotState() {
|
|
21009
21162
|
return this.screenshotManager?.getState() || null;
|
|
21010
21163
|
}
|
|
21164
|
+
/**
|
|
21165
|
+
* 更新截图配置(动态更新,无需重新初始化)
|
|
21166
|
+
* @param options 要更新的配置项(部分更新)
|
|
21167
|
+
*/
|
|
21168
|
+
updateScreenshotOptions(options) {
|
|
21169
|
+
if (!this.screenshotManager) {
|
|
21170
|
+
console.warn('截图管理器未初始化,无法更新配置');
|
|
21171
|
+
return;
|
|
21172
|
+
}
|
|
21173
|
+
this.screenshotManager.updateOptions(options);
|
|
21174
|
+
console.log('📸 截图配置已更新:', options);
|
|
21175
|
+
}
|
|
21011
21176
|
/**
|
|
21012
21177
|
* 销毁 SDK
|
|
21013
21178
|
*/
|
|
@@ -21173,6 +21338,10 @@ const getScreenshotState = () => {
|
|
|
21173
21338
|
const sdk = getInstance();
|
|
21174
21339
|
return sdk.getScreenshotState();
|
|
21175
21340
|
};
|
|
21341
|
+
const updateScreenshotOptions = (options) => {
|
|
21342
|
+
const sdk = getInstance();
|
|
21343
|
+
sdk.updateScreenshotOptions(options);
|
|
21344
|
+
};
|
|
21176
21345
|
// 默认导出
|
|
21177
21346
|
var index = {
|
|
21178
21347
|
init,
|
|
@@ -21193,7 +21362,8 @@ var index = {
|
|
|
21193
21362
|
captureScreenshot,
|
|
21194
21363
|
enableScreenshot,
|
|
21195
21364
|
getScreenshotState,
|
|
21365
|
+
updateScreenshotOptions,
|
|
21196
21366
|
destroy
|
|
21197
21367
|
};
|
|
21198
21368
|
|
|
21199
|
-
export { CustomerServiceSDK, captureScreenshot, clearNotification, closeChat, index as default, destroy, enableScreenshot, getConnectionStatus, getInstance, getScreenshotState, hideIcon, init, isChatOpen, openChat, sendToIframe, setIconCoordinates, setIconPosition, setIconStyle, setScreenshotTarget, showIcon, showNotification };
|
|
21369
|
+
export { CustomerServiceSDK, captureScreenshot, clearNotification, closeChat, index as default, destroy, enableScreenshot, getConnectionStatus, getInstance, getScreenshotState, hideIcon, init, isChatOpen, openChat, sendToIframe, setIconCoordinates, setIconPosition, setIconStyle, setScreenshotTarget, showIcon, showNotification, updateScreenshotOptions };
|