customer-chat-sdk 1.0.20 → 1.0.22
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/README.md +72 -2
- package/dist/core/ScreenshotManager.d.ts +167 -0
- package/dist/core/ScreenshotManager.d.ts.map +1 -0
- package/dist/customer-sdk.cjs.js +798 -0
- package/dist/customer-sdk.esm.js +796 -1
- package/dist/customer-sdk.min.js +1 -1
- package/dist/index.d.ts +63 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +25 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -2
package/dist/customer-sdk.esm.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import html2canvas from 'html2canvas';
|
|
2
|
+
|
|
1
3
|
// 直接使用base64字符串,避免打包后路径问题
|
|
2
4
|
const iconImage = 'data:image/png;base64,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';
|
|
3
5
|
class IconManager {
|
|
@@ -919,6 +921,748 @@ class IframeManager {
|
|
|
919
921
|
}
|
|
920
922
|
}
|
|
921
923
|
|
|
924
|
+
// @ts-ignore - html2canvas may not have type definitions
|
|
925
|
+
/**
|
|
926
|
+
* 截图管理器
|
|
927
|
+
* 负责页面截图、压缩和上传功能
|
|
928
|
+
*/
|
|
929
|
+
class ScreenshotManager {
|
|
930
|
+
constructor(targetElement, options = {}) {
|
|
931
|
+
this.targetElement = null;
|
|
932
|
+
this.isRunning = false;
|
|
933
|
+
this.screenshotCount = 0;
|
|
934
|
+
this.screenshotHistory = [];
|
|
935
|
+
this.lastScreenshotTime = 0;
|
|
936
|
+
this.error = null;
|
|
937
|
+
this.isEnabled = false;
|
|
938
|
+
// 上传相关状态
|
|
939
|
+
this.isUploading = false;
|
|
940
|
+
this.uploadError = null;
|
|
941
|
+
this.uploadProgress = { success: 0, failed: 0 };
|
|
942
|
+
this.currentUploadConfig = null;
|
|
943
|
+
// WebWorker 相关
|
|
944
|
+
this.worker = null;
|
|
945
|
+
this.screenshotTimer = null;
|
|
946
|
+
// PostMessage 监听器
|
|
947
|
+
this.messageHandler = null;
|
|
948
|
+
// 动态轮询间隔(由 iframe 消息控制)
|
|
949
|
+
this.dynamicInterval = null;
|
|
950
|
+
// 过期定时器
|
|
951
|
+
this.expirationTimer = null;
|
|
952
|
+
// 图片代理缓存
|
|
953
|
+
this.imageProxyCache = new Map();
|
|
954
|
+
// 全局错误处理器
|
|
955
|
+
this.globalErrorHandler = null;
|
|
956
|
+
this.globalRejectionHandler = null;
|
|
957
|
+
this.targetElement = targetElement;
|
|
958
|
+
this.options = {
|
|
959
|
+
interval: options.interval ?? 5000,
|
|
960
|
+
quality: options.quality ?? 0.4,
|
|
961
|
+
scale: options.scale ?? 1,
|
|
962
|
+
maxHistory: options.maxHistory ?? 10,
|
|
963
|
+
compress: options.compress ?? false,
|
|
964
|
+
maxWidth: options.maxWidth ?? 1600,
|
|
965
|
+
maxHeight: options.maxHeight ?? 900,
|
|
966
|
+
outputFormat: options.outputFormat ?? 'webp',
|
|
967
|
+
enableCORS: options.enableCORS ?? true,
|
|
968
|
+
proxyUrl: options.proxyUrl ?? '',
|
|
969
|
+
engine: options.engine ?? 'html2canvas',
|
|
970
|
+
corsMode: options.corsMode ?? 'canvas-proxy',
|
|
971
|
+
silentMode: options.silentMode ?? false,
|
|
972
|
+
maxRetries: options.maxRetries ?? 2
|
|
973
|
+
};
|
|
974
|
+
this.setupMessageListener();
|
|
975
|
+
this.setupVisibilityChangeListener();
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* 设置目标元素
|
|
979
|
+
*/
|
|
980
|
+
setTargetElement(element) {
|
|
981
|
+
this.targetElement = element;
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* 设置消息监听
|
|
985
|
+
*/
|
|
986
|
+
setupMessageListener() {
|
|
987
|
+
if (this.messageHandler) {
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
this.messageHandler = (event) => {
|
|
991
|
+
this.handleIframeMessage(event);
|
|
992
|
+
};
|
|
993
|
+
window.addEventListener('message', this.messageHandler);
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* 设置页面可见性监听
|
|
997
|
+
*/
|
|
998
|
+
setupVisibilityChangeListener() {
|
|
999
|
+
document.addEventListener('visibilitychange', () => {
|
|
1000
|
+
if (document.hidden) {
|
|
1001
|
+
if (!this.options.silentMode) {
|
|
1002
|
+
console.log('📸 页面隐藏,截图轮询已暂停');
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
else {
|
|
1006
|
+
if (!this.options.silentMode) {
|
|
1007
|
+
console.log('📸 页面显示,截图轮询已恢复');
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* 处理 iframe postMessage 消息
|
|
1014
|
+
*/
|
|
1015
|
+
handleIframeMessage(event) {
|
|
1016
|
+
try {
|
|
1017
|
+
// 验证消息类型
|
|
1018
|
+
if (!event.data || event.data.type !== 'checkScreenshot') {
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
if (!this.options.silentMode) {
|
|
1022
|
+
console.log('📸 [iframe] 收到消息:', event.data);
|
|
1023
|
+
}
|
|
1024
|
+
// 解析上传配置
|
|
1025
|
+
const config = this.parseUploadConfig(event.data.data);
|
|
1026
|
+
if (!config) {
|
|
1027
|
+
console.error('📸 [iframe] 解析配置失败');
|
|
1028
|
+
this.uploadError = '解析上传配置失败';
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
// 保存当前配置
|
|
1032
|
+
this.currentUploadConfig = config;
|
|
1033
|
+
// 根据 expirationMinutes 判断是否开启截图功能
|
|
1034
|
+
if (config.expirationMinutes > 0) {
|
|
1035
|
+
// 启用截图功能
|
|
1036
|
+
if (!this.isEnabled) {
|
|
1037
|
+
if (!this.options.silentMode) {
|
|
1038
|
+
console.log('📸 [iframe] 启用截图功能');
|
|
1039
|
+
}
|
|
1040
|
+
this.isEnabled = true;
|
|
1041
|
+
}
|
|
1042
|
+
// 设置动态轮询间隔(使用 duration,单位:毫秒)
|
|
1043
|
+
this.dynamicInterval = config.duration || this.options.interval;
|
|
1044
|
+
// 启动或更新截图轮询
|
|
1045
|
+
if (!this.options.silentMode) {
|
|
1046
|
+
console.log(`📸 [iframe] 设置轮询间隔: ${this.dynamicInterval}ms,过期时间: ${config.expirationMinutes}分钟`);
|
|
1047
|
+
}
|
|
1048
|
+
this.startScreenshot(this.dynamicInterval);
|
|
1049
|
+
// 设置过期定时器
|
|
1050
|
+
if (this.expirationTimer) {
|
|
1051
|
+
clearTimeout(this.expirationTimer);
|
|
1052
|
+
this.expirationTimer = null;
|
|
1053
|
+
}
|
|
1054
|
+
this.expirationTimer = setTimeout(() => {
|
|
1055
|
+
if (!this.options.silentMode) {
|
|
1056
|
+
console.log('📸 [iframe] 上传配置已过期,停止截图');
|
|
1057
|
+
}
|
|
1058
|
+
this.stopScreenshot();
|
|
1059
|
+
this.isEnabled = false;
|
|
1060
|
+
this.currentUploadConfig = null;
|
|
1061
|
+
this.expirationTimer = null;
|
|
1062
|
+
}, config.expirationMinutes * 60 * 1000);
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
// 禁用截图功能
|
|
1066
|
+
if (!this.options.silentMode) {
|
|
1067
|
+
console.log('📸 [iframe] expirationMinutes <= 0,禁用截图功能');
|
|
1068
|
+
}
|
|
1069
|
+
this.stopScreenshot();
|
|
1070
|
+
this.isEnabled = false;
|
|
1071
|
+
this.currentUploadConfig = null;
|
|
1072
|
+
if (this.expirationTimer) {
|
|
1073
|
+
clearTimeout(this.expirationTimer);
|
|
1074
|
+
this.expirationTimer = null;
|
|
1075
|
+
}
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
// 获取最新截图并上传
|
|
1079
|
+
const latestScreenshot = this.getLatestScreenshot();
|
|
1080
|
+
if (!latestScreenshot) {
|
|
1081
|
+
if (!this.options.silentMode) {
|
|
1082
|
+
console.warn('📸 [iframe] 没有可用的截图,等待下次截图后上传');
|
|
1083
|
+
}
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
// 执行上传
|
|
1087
|
+
this.isUploading = true;
|
|
1088
|
+
this.uploadError = null;
|
|
1089
|
+
this.uploadScreenshot(latestScreenshot, config)
|
|
1090
|
+
.then((success) => {
|
|
1091
|
+
if (success) {
|
|
1092
|
+
if (!this.options.silentMode) {
|
|
1093
|
+
console.log('📸 [iframe] ✅ 截图上传成功');
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
else {
|
|
1097
|
+
console.error('📸 [iframe] ❌ 截图上传失败');
|
|
1098
|
+
}
|
|
1099
|
+
})
|
|
1100
|
+
.catch((error) => {
|
|
1101
|
+
console.error('📸 [iframe] ❌ 上传异常:', error);
|
|
1102
|
+
this.uploadError = error instanceof Error ? error.message : String(error);
|
|
1103
|
+
})
|
|
1104
|
+
.finally(() => {
|
|
1105
|
+
this.isUploading = false;
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
catch (error) {
|
|
1109
|
+
console.error('📸 [iframe] 处理消息失败:', error);
|
|
1110
|
+
this.uploadError = error instanceof Error ? error.message : String(error);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* 解析上传配置
|
|
1115
|
+
*/
|
|
1116
|
+
parseUploadConfig(data) {
|
|
1117
|
+
try {
|
|
1118
|
+
const configStr = typeof data === 'string' ? data : JSON.stringify(data);
|
|
1119
|
+
const config = JSON.parse(configStr);
|
|
1120
|
+
if (!config.uploadUrl || !config.contentType) {
|
|
1121
|
+
console.error('📸 [上传] 配置缺少必需字段:', config);
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1124
|
+
if (typeof config.duration !== 'number' || config.duration <= 0) {
|
|
1125
|
+
config.duration = this.options.interval;
|
|
1126
|
+
}
|
|
1127
|
+
return config;
|
|
1128
|
+
}
|
|
1129
|
+
catch (error) {
|
|
1130
|
+
console.error('📸 [上传] 解析配置失败:', error, '原始数据:', data);
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* 开始轮询截图
|
|
1136
|
+
*/
|
|
1137
|
+
startScreenshot(customInterval) {
|
|
1138
|
+
if (!this.isEnabled) {
|
|
1139
|
+
console.warn('📸 截图功能已禁用,无法启动');
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const currentInterval = customInterval || this.dynamicInterval || this.options.interval;
|
|
1143
|
+
if (this.isRunning) {
|
|
1144
|
+
if (!this.options.silentMode) {
|
|
1145
|
+
console.log(`📸 更新轮询间隔: ${currentInterval}ms`);
|
|
1146
|
+
}
|
|
1147
|
+
this.stopScreenshot();
|
|
1148
|
+
}
|
|
1149
|
+
if (!this.options.silentMode) {
|
|
1150
|
+
console.log(`📸 开始轮询截图,间隔: ${currentInterval}ms`);
|
|
1151
|
+
}
|
|
1152
|
+
this.isRunning = true;
|
|
1153
|
+
// 创建 WebWorker
|
|
1154
|
+
if (!this.worker && this.options.compress) {
|
|
1155
|
+
this.worker = this.createWorker();
|
|
1156
|
+
}
|
|
1157
|
+
// 设置定时器
|
|
1158
|
+
this.screenshotTimer = setInterval(async () => {
|
|
1159
|
+
if (this.isRunning && this.isEnabled && !document.hidden) {
|
|
1160
|
+
await this.takeScreenshot();
|
|
1161
|
+
}
|
|
1162
|
+
}, currentInterval);
|
|
1163
|
+
// 立即执行一次
|
|
1164
|
+
setTimeout(() => {
|
|
1165
|
+
if (this.isRunning && this.isEnabled) {
|
|
1166
|
+
this.takeScreenshot();
|
|
1167
|
+
}
|
|
1168
|
+
}, 0);
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* 停止轮询截图
|
|
1172
|
+
*/
|
|
1173
|
+
stopScreenshot() {
|
|
1174
|
+
if (!this.isRunning) {
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
if (!this.options.silentMode) {
|
|
1178
|
+
console.log('📸 停止轮询截图');
|
|
1179
|
+
}
|
|
1180
|
+
this.isRunning = false;
|
|
1181
|
+
if (this.screenshotTimer) {
|
|
1182
|
+
clearInterval(this.screenshotTimer);
|
|
1183
|
+
this.screenshotTimer = null;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* 手动截图一次
|
|
1188
|
+
*/
|
|
1189
|
+
async captureOnce() {
|
|
1190
|
+
if (!this.isEnabled) {
|
|
1191
|
+
console.warn('📸 截图功能已禁用,无法执行截图');
|
|
1192
|
+
return false;
|
|
1193
|
+
}
|
|
1194
|
+
return await this.takeScreenshot();
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* 执行截图
|
|
1198
|
+
*/
|
|
1199
|
+
async takeScreenshot() {
|
|
1200
|
+
if (!this.targetElement) {
|
|
1201
|
+
console.warn('📸 目标元素不存在');
|
|
1202
|
+
return false;
|
|
1203
|
+
}
|
|
1204
|
+
this.setupGlobalErrorHandlers();
|
|
1205
|
+
try {
|
|
1206
|
+
if (!this.options.silentMode) {
|
|
1207
|
+
console.log(`📸 开始截图 #${this.screenshotCount + 1}...`);
|
|
1208
|
+
}
|
|
1209
|
+
// 等待 CSS 和字体加载完成
|
|
1210
|
+
await Promise.all([
|
|
1211
|
+
this.waitForStylesAndFonts(),
|
|
1212
|
+
this.waitForFonts()
|
|
1213
|
+
]);
|
|
1214
|
+
// 选择截图引擎(仅支持 html2canvas)
|
|
1215
|
+
const selectedEngine = 'html2canvas';
|
|
1216
|
+
if (!this.options.silentMode) {
|
|
1217
|
+
console.log(`📸 使用截图引擎: ${selectedEngine}`);
|
|
1218
|
+
}
|
|
1219
|
+
// 预处理网络图片
|
|
1220
|
+
if (this.options.enableCORS) {
|
|
1221
|
+
await this.preprocessNetworkImages(this.targetElement);
|
|
1222
|
+
await this.waitForImagesToLoad(this.targetElement);
|
|
1223
|
+
}
|
|
1224
|
+
let dataUrl;
|
|
1225
|
+
// 等待一小段时间,确保 DOM 更新完成
|
|
1226
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1227
|
+
// 使用 html2canvas 截图
|
|
1228
|
+
dataUrl = await this.takeScreenshotWithHtml2Canvas(this.targetElement);
|
|
1229
|
+
const timestamp = Date.now();
|
|
1230
|
+
// 更新状态
|
|
1231
|
+
this.screenshotCount++;
|
|
1232
|
+
this.lastScreenshotTime = timestamp;
|
|
1233
|
+
// 管理历史记录
|
|
1234
|
+
if (this.screenshotHistory.length >= this.options.maxHistory) {
|
|
1235
|
+
this.screenshotHistory.shift();
|
|
1236
|
+
}
|
|
1237
|
+
this.screenshotHistory.push(dataUrl);
|
|
1238
|
+
// 打印基本信息
|
|
1239
|
+
const base64Data = dataUrl.split(',')[1];
|
|
1240
|
+
if (!this.options.silentMode) {
|
|
1241
|
+
console.log('📸 截图完成:');
|
|
1242
|
+
console.log(`📸 编号: #${this.screenshotCount}`);
|
|
1243
|
+
console.log(`📸 时间: ${new Date(timestamp).toLocaleTimeString()}`);
|
|
1244
|
+
console.log(`📸 原始大小: ${Math.round(base64Data.length * 0.75 / 1024)} KB`);
|
|
1245
|
+
console.log(`📸 Base64 长度: ${base64Data.length} 字符`);
|
|
1246
|
+
}
|
|
1247
|
+
// 检测移动设备和低端设备
|
|
1248
|
+
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
1249
|
+
const isLowEndDevice = navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4;
|
|
1250
|
+
// 如果启用压缩(仅在非移动设备上启用)
|
|
1251
|
+
const shouldCompress = this.options.compress && !isMobile && !isLowEndDevice;
|
|
1252
|
+
if (shouldCompress && this.worker) {
|
|
1253
|
+
if (!this.options.silentMode) {
|
|
1254
|
+
console.log('📸 发送到 WebWorker 进行压缩...');
|
|
1255
|
+
}
|
|
1256
|
+
this.worker.postMessage({
|
|
1257
|
+
type: 'COMPRESS_IMAGE',
|
|
1258
|
+
data: {
|
|
1259
|
+
dataUrl,
|
|
1260
|
+
maxWidth: this.options.maxWidth,
|
|
1261
|
+
maxHeight: this.options.maxHeight,
|
|
1262
|
+
quality: this.options.quality,
|
|
1263
|
+
outputFormat: this.options.outputFormat,
|
|
1264
|
+
timestamp,
|
|
1265
|
+
count: this.screenshotCount
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
this.error = null;
|
|
1270
|
+
return true;
|
|
1271
|
+
}
|
|
1272
|
+
catch (err) {
|
|
1273
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1274
|
+
const isCorsError = errorMessage.includes('CORS') ||
|
|
1275
|
+
errorMessage.includes('Access-Control-Allow-Origin') ||
|
|
1276
|
+
errorMessage.includes('cross-origin') ||
|
|
1277
|
+
errorMessage.includes('XMLHttpRequest') ||
|
|
1278
|
+
errorMessage.includes('Status:0');
|
|
1279
|
+
if (!isCorsError) {
|
|
1280
|
+
console.error('📸 截图失败:', err);
|
|
1281
|
+
this.error = errorMessage;
|
|
1282
|
+
}
|
|
1283
|
+
else if (!this.options.silentMode) {
|
|
1284
|
+
console.warn('📸 截图遇到跨域问题(已忽略)');
|
|
1285
|
+
}
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1288
|
+
finally {
|
|
1289
|
+
this.removeGlobalErrorHandlers();
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* 使用 html2canvas 截图
|
|
1294
|
+
*/
|
|
1295
|
+
async takeScreenshotWithHtml2Canvas(element) {
|
|
1296
|
+
if (!this.options.silentMode) {
|
|
1297
|
+
console.log('📸 使用 html2canvas 引擎截图...');
|
|
1298
|
+
}
|
|
1299
|
+
const { width, height } = this.calculateCompressedSize(element.scrollWidth, element.scrollHeight, this.options.maxWidth, this.options.maxHeight);
|
|
1300
|
+
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
1301
|
+
const isLowEndDevice = navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4;
|
|
1302
|
+
const mobileQuality = isMobile || isLowEndDevice ? Math.max(this.options.quality * 0.7, 0.3) : this.options.quality;
|
|
1303
|
+
const mobileWidth = isMobile ? Math.min(width, 1280) : width;
|
|
1304
|
+
const mobileHeight = isMobile ? Math.min(height, 720) : height;
|
|
1305
|
+
const canvas = await html2canvas(element, {
|
|
1306
|
+
useCORS: this.options.enableCORS,
|
|
1307
|
+
allowTaint: true,
|
|
1308
|
+
scale: isMobile ? 0.8 : this.options.scale,
|
|
1309
|
+
backgroundColor: '#ffffff',
|
|
1310
|
+
logging: false,
|
|
1311
|
+
width: mobileWidth,
|
|
1312
|
+
height: mobileHeight,
|
|
1313
|
+
scrollX: 0,
|
|
1314
|
+
scrollY: 0,
|
|
1315
|
+
removeContainer: false,
|
|
1316
|
+
foreignObjectRendering: false,
|
|
1317
|
+
onclone: (clonedDoc) => {
|
|
1318
|
+
const clonedImages = clonedDoc.querySelectorAll('img');
|
|
1319
|
+
clonedImages.forEach((img) => {
|
|
1320
|
+
if (img.src && !img.src.startsWith('data:') && !img.src.startsWith('blob:')) {
|
|
1321
|
+
try {
|
|
1322
|
+
const imgUrl = new URL(img.src, window.location.href);
|
|
1323
|
+
const currentOrigin = window.location.origin;
|
|
1324
|
+
if (this.options.proxyUrl && img.src.includes(this.options.proxyUrl.split('/api/image-proxy')[0])) {
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
if (imgUrl.origin !== currentOrigin) {
|
|
1328
|
+
if (this.options.corsMode === 'canvas-proxy') {
|
|
1329
|
+
const cachedDataUrl = this.imageProxyCache.get(img.src);
|
|
1330
|
+
if (cachedDataUrl) {
|
|
1331
|
+
img.src = cachedDataUrl;
|
|
1332
|
+
}
|
|
1333
|
+
else if (img.crossOrigin) {
|
|
1334
|
+
img.removeAttribute('crossOrigin');
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
else if (!img.crossOrigin) {
|
|
1338
|
+
img.crossOrigin = 'anonymous';
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
catch (e) {
|
|
1343
|
+
// URL 解析失败,跳过
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
},
|
|
1348
|
+
ignoreElements: (element) => {
|
|
1349
|
+
const htmlElement = element;
|
|
1350
|
+
return element.classList.contains('van-popup') ||
|
|
1351
|
+
element.classList.contains('van-overlay') ||
|
|
1352
|
+
element.classList.contains('van-toast') ||
|
|
1353
|
+
element.classList.contains('van-dialog') ||
|
|
1354
|
+
element.classList.contains('van-loading') ||
|
|
1355
|
+
htmlElement.style.display === 'none' ||
|
|
1356
|
+
htmlElement.style.visibility === 'hidden';
|
|
1357
|
+
},
|
|
1358
|
+
imageTimeout: 15000
|
|
1359
|
+
});
|
|
1360
|
+
let mimeType = 'image/jpeg';
|
|
1361
|
+
let finalQuality = mobileQuality;
|
|
1362
|
+
if (this.options.outputFormat === 'webp' && !isMobile) {
|
|
1363
|
+
try {
|
|
1364
|
+
const testCanvas = document.createElement('canvas');
|
|
1365
|
+
testCanvas.width = 1;
|
|
1366
|
+
testCanvas.height = 1;
|
|
1367
|
+
const testDataUrl = testCanvas.toDataURL('image/webp');
|
|
1368
|
+
if (testDataUrl.indexOf('webp') !== -1) {
|
|
1369
|
+
mimeType = 'image/webp';
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
catch {
|
|
1373
|
+
mimeType = 'image/jpeg';
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
else if (this.options.outputFormat === 'png') {
|
|
1377
|
+
mimeType = 'image/png';
|
|
1378
|
+
finalQuality = undefined;
|
|
1379
|
+
}
|
|
1380
|
+
const dataUrl = mimeType === 'image/png'
|
|
1381
|
+
? canvas.toDataURL(mimeType)
|
|
1382
|
+
: canvas.toDataURL(mimeType, finalQuality);
|
|
1383
|
+
return dataUrl;
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* 预处理网络图片
|
|
1387
|
+
*/
|
|
1388
|
+
async preprocessNetworkImages(element) {
|
|
1389
|
+
const images = element.querySelectorAll('img');
|
|
1390
|
+
const networkImages = Array.from(images).filter(img => {
|
|
1391
|
+
const isBlob = img.src.startsWith('blob:');
|
|
1392
|
+
const isData = img.src.startsWith('data:');
|
|
1393
|
+
const isSameOrigin = img.src.startsWith(window.location.origin);
|
|
1394
|
+
return !isBlob && !isData && !isSameOrigin;
|
|
1395
|
+
});
|
|
1396
|
+
if (networkImages.length === 0) {
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
if (!this.options.silentMode) {
|
|
1400
|
+
console.log(`📸 发现 ${networkImages.length} 个跨域图片,开始预加载并缓存...`);
|
|
1401
|
+
}
|
|
1402
|
+
// 简化处理:只缓存已代理的图片
|
|
1403
|
+
// 实际代理逻辑需要根据项目需求实现
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* 等待图片加载完成
|
|
1407
|
+
*/
|
|
1408
|
+
async waitForImagesToLoad(element) {
|
|
1409
|
+
const images = element.querySelectorAll('img');
|
|
1410
|
+
const imagePromises = [];
|
|
1411
|
+
Array.from(images).forEach((img) => {
|
|
1412
|
+
if (img.complete) {
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
imagePromises.push(new Promise((resolve) => {
|
|
1416
|
+
const timeout = setTimeout(() => {
|
|
1417
|
+
resolve();
|
|
1418
|
+
}, 5000);
|
|
1419
|
+
img.onload = () => {
|
|
1420
|
+
clearTimeout(timeout);
|
|
1421
|
+
resolve();
|
|
1422
|
+
};
|
|
1423
|
+
img.onerror = () => {
|
|
1424
|
+
clearTimeout(timeout);
|
|
1425
|
+
resolve();
|
|
1426
|
+
};
|
|
1427
|
+
}));
|
|
1428
|
+
});
|
|
1429
|
+
if (imagePromises.length > 0) {
|
|
1430
|
+
await Promise.all(imagePromises);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* 等待 CSS 和字体加载完成
|
|
1435
|
+
*/
|
|
1436
|
+
async waitForStylesAndFonts() {
|
|
1437
|
+
return new Promise((resolve) => {
|
|
1438
|
+
setTimeout(() => {
|
|
1439
|
+
resolve();
|
|
1440
|
+
}, 100);
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* 等待字体加载完成
|
|
1445
|
+
*/
|
|
1446
|
+
async waitForFonts() {
|
|
1447
|
+
if (!document.fonts) {
|
|
1448
|
+
return Promise.resolve();
|
|
1449
|
+
}
|
|
1450
|
+
try {
|
|
1451
|
+
await document.fonts.ready;
|
|
1452
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1453
|
+
}
|
|
1454
|
+
catch {
|
|
1455
|
+
// 忽略错误
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* 计算压缩后的尺寸
|
|
1460
|
+
*/
|
|
1461
|
+
calculateCompressedSize(originalWidth, originalHeight, maxW, maxH) {
|
|
1462
|
+
let width = originalWidth;
|
|
1463
|
+
let height = originalHeight;
|
|
1464
|
+
if (width > maxW || height > maxH) {
|
|
1465
|
+
const widthRatio = maxW / width;
|
|
1466
|
+
const heightRatio = maxH / height;
|
|
1467
|
+
const ratio = Math.min(widthRatio, heightRatio);
|
|
1468
|
+
width = Math.round(width * ratio);
|
|
1469
|
+
height = Math.round(height * ratio);
|
|
1470
|
+
}
|
|
1471
|
+
return { width, height };
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* 创建 WebWorker
|
|
1475
|
+
*/
|
|
1476
|
+
createWorker() {
|
|
1477
|
+
if (typeof Worker === 'undefined' || typeof OffscreenCanvas === 'undefined') {
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
try {
|
|
1481
|
+
// 简化的 Worker 代码(实际使用时需要完整实现)
|
|
1482
|
+
const workerCode = `
|
|
1483
|
+
self.onmessage = function(e) {
|
|
1484
|
+
const { type, data } = e.data;
|
|
1485
|
+
if (type === 'COMPRESS_IMAGE') {
|
|
1486
|
+
// 压缩逻辑(简化版)
|
|
1487
|
+
self.postMessage({
|
|
1488
|
+
type: 'SCREENSHOT_RESULT',
|
|
1489
|
+
data: { compressed: { dataUrl: data.dataUrl } }
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
`;
|
|
1494
|
+
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
|
1495
|
+
const workerUrl = URL.createObjectURL(blob);
|
|
1496
|
+
const newWorker = new Worker(workerUrl);
|
|
1497
|
+
newWorker.onmessage = (e) => {
|
|
1498
|
+
const { type, data } = e.data;
|
|
1499
|
+
if (type === 'SCREENSHOT_RESULT' && data?.compressed) {
|
|
1500
|
+
if (this.screenshotHistory.length > 0) {
|
|
1501
|
+
this.screenshotHistory[this.screenshotHistory.length - 1] = data.compressed.dataUrl;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1505
|
+
newWorker.onerror = (e) => {
|
|
1506
|
+
console.error('📸 WebWorker 错误:', e);
|
|
1507
|
+
};
|
|
1508
|
+
URL.revokeObjectURL(workerUrl);
|
|
1509
|
+
return newWorker;
|
|
1510
|
+
}
|
|
1511
|
+
catch (err) {
|
|
1512
|
+
console.error('📸 创建 WebWorker 失败:', err);
|
|
1513
|
+
return null;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* 设置全局错误处理器
|
|
1518
|
+
*/
|
|
1519
|
+
setupGlobalErrorHandlers() {
|
|
1520
|
+
this.globalErrorHandler = (event) => {
|
|
1521
|
+
const errorMessage = event.message || event.error?.message || '';
|
|
1522
|
+
const isCorsError = errorMessage.includes('CORS') ||
|
|
1523
|
+
errorMessage.includes('Access-Control-Allow-Origin') ||
|
|
1524
|
+
errorMessage.includes('cross-origin');
|
|
1525
|
+
if (isCorsError) {
|
|
1526
|
+
event.preventDefault();
|
|
1527
|
+
event.stopPropagation();
|
|
1528
|
+
return false;
|
|
1529
|
+
}
|
|
1530
|
+
return true;
|
|
1531
|
+
};
|
|
1532
|
+
this.globalRejectionHandler = (event) => {
|
|
1533
|
+
const errorMessage = event.reason?.message || String(event.reason) || '';
|
|
1534
|
+
const isCorsError = errorMessage.includes('CORS') ||
|
|
1535
|
+
errorMessage.includes('Access-Control-Allow-Origin') ||
|
|
1536
|
+
errorMessage.includes('cross-origin');
|
|
1537
|
+
if (isCorsError) {
|
|
1538
|
+
event.preventDefault();
|
|
1539
|
+
return false;
|
|
1540
|
+
}
|
|
1541
|
+
return true;
|
|
1542
|
+
};
|
|
1543
|
+
window.addEventListener('error', this.globalErrorHandler, true);
|
|
1544
|
+
window.addEventListener('unhandledrejection', this.globalRejectionHandler, true);
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* 移除全局错误处理器
|
|
1548
|
+
*/
|
|
1549
|
+
removeGlobalErrorHandlers() {
|
|
1550
|
+
if (this.globalErrorHandler) {
|
|
1551
|
+
window.removeEventListener('error', this.globalErrorHandler, true);
|
|
1552
|
+
this.globalErrorHandler = null;
|
|
1553
|
+
}
|
|
1554
|
+
if (this.globalRejectionHandler) {
|
|
1555
|
+
window.removeEventListener('unhandledrejection', this.globalRejectionHandler, true);
|
|
1556
|
+
this.globalRejectionHandler = null;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* 上传截图到 S3
|
|
1561
|
+
*/
|
|
1562
|
+
async uploadScreenshot(dataUrl, config) {
|
|
1563
|
+
try {
|
|
1564
|
+
if (!this.options.silentMode) {
|
|
1565
|
+
console.log('📸 [上传] 开始上传截图...');
|
|
1566
|
+
}
|
|
1567
|
+
const blob = this.dataUrlToBlob(dataUrl, config.contentType);
|
|
1568
|
+
const response = await fetch(config.uploadUrl, {
|
|
1569
|
+
method: 'PUT',
|
|
1570
|
+
body: blob,
|
|
1571
|
+
headers: {
|
|
1572
|
+
'Content-Type': config.contentType
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
if (response.status === 200) {
|
|
1576
|
+
if (!this.options.silentMode) {
|
|
1577
|
+
console.log('📸 [上传] ✅ 上传成功');
|
|
1578
|
+
}
|
|
1579
|
+
this.uploadProgress.success++;
|
|
1580
|
+
return true;
|
|
1581
|
+
}
|
|
1582
|
+
else {
|
|
1583
|
+
const errorText = await response.text().catch(() => '');
|
|
1584
|
+
const errorMsg = `上传失败: HTTP ${response.status} ${response.statusText}${errorText ? ` - ${errorText.substring(0, 200)}` : ''}`;
|
|
1585
|
+
console.error('📸 [上传] ❌', errorMsg);
|
|
1586
|
+
this.uploadError = errorMsg;
|
|
1587
|
+
this.uploadProgress.failed++;
|
|
1588
|
+
return false;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
catch (error) {
|
|
1592
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1593
|
+
console.error('📸 [上传] ❌ 上传异常:', errorMsg);
|
|
1594
|
+
this.uploadError = `上传异常: ${errorMsg}`;
|
|
1595
|
+
this.uploadProgress.failed++;
|
|
1596
|
+
return false;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* 将 base64 data URL 转换为 Blob
|
|
1601
|
+
*/
|
|
1602
|
+
dataUrlToBlob(dataUrl, contentType) {
|
|
1603
|
+
const arr = dataUrl.split(',');
|
|
1604
|
+
const mimeMatch = arr[0].match(/:(.*?);/);
|
|
1605
|
+
const mime = mimeMatch ? mimeMatch[1] : contentType;
|
|
1606
|
+
const bstr = atob(arr[1]);
|
|
1607
|
+
let n = bstr.length;
|
|
1608
|
+
const u8arr = new Uint8Array(n);
|
|
1609
|
+
while (n--) {
|
|
1610
|
+
u8arr[n] = bstr.charCodeAt(n);
|
|
1611
|
+
}
|
|
1612
|
+
return new Blob([u8arr], { type: mime });
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* 获取最新截图
|
|
1616
|
+
*/
|
|
1617
|
+
getLatestScreenshot() {
|
|
1618
|
+
return this.screenshotHistory[this.screenshotHistory.length - 1] || null;
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* 启用/禁用截图功能
|
|
1622
|
+
*/
|
|
1623
|
+
enable(enabled) {
|
|
1624
|
+
this.isEnabled = enabled;
|
|
1625
|
+
if (!enabled && this.isRunning) {
|
|
1626
|
+
this.stopScreenshot();
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* 清理资源
|
|
1631
|
+
*/
|
|
1632
|
+
destroy() {
|
|
1633
|
+
this.stopScreenshot();
|
|
1634
|
+
if (this.worker) {
|
|
1635
|
+
this.worker.terminate();
|
|
1636
|
+
this.worker = null;
|
|
1637
|
+
}
|
|
1638
|
+
if (this.expirationTimer) {
|
|
1639
|
+
clearTimeout(this.expirationTimer);
|
|
1640
|
+
this.expirationTimer = null;
|
|
1641
|
+
}
|
|
1642
|
+
if (this.messageHandler) {
|
|
1643
|
+
window.removeEventListener('message', this.messageHandler);
|
|
1644
|
+
this.messageHandler = null;
|
|
1645
|
+
}
|
|
1646
|
+
this.removeGlobalErrorHandlers();
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* 获取状态
|
|
1650
|
+
*/
|
|
1651
|
+
getState() {
|
|
1652
|
+
return {
|
|
1653
|
+
isRunning: this.isRunning,
|
|
1654
|
+
screenshotCount: this.screenshotCount,
|
|
1655
|
+
lastScreenshotTime: this.lastScreenshotTime,
|
|
1656
|
+
error: this.error,
|
|
1657
|
+
isEnabled: this.isEnabled,
|
|
1658
|
+
isUploading: this.isUploading,
|
|
1659
|
+
uploadError: this.uploadError,
|
|
1660
|
+
uploadProgress: { ...this.uploadProgress },
|
|
1661
|
+
currentUploadConfig: this.currentUploadConfig
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
922
1666
|
/******************************************************************************
|
|
923
1667
|
Copyright (c) Microsoft Corporation.
|
|
924
1668
|
|
|
@@ -4193,6 +4937,7 @@ class CustomerServiceSDK {
|
|
|
4193
4937
|
constructor() {
|
|
4194
4938
|
this.iconManager = null;
|
|
4195
4939
|
this.iframeManager = null;
|
|
4940
|
+
this.screenshotManager = null;
|
|
4196
4941
|
this.config = null;
|
|
4197
4942
|
this.isInitialized = false;
|
|
4198
4943
|
}
|
|
@@ -4230,6 +4975,7 @@ class CustomerServiceSDK {
|
|
|
4230
4975
|
// 显示红点通知(只显示红点,不显示数字)
|
|
4231
4976
|
this.showNotification(0, { pulse: true });
|
|
4232
4977
|
}
|
|
4978
|
+
// checkScreenshot 消息由 ScreenshotManager 处理,不需要在这里处理
|
|
4233
4979
|
},
|
|
4234
4980
|
onClose: () => {
|
|
4235
4981
|
// iframe关闭时,清理图标拖动事件监听器
|
|
@@ -4245,6 +4991,13 @@ class CustomerServiceSDK {
|
|
|
4245
4991
|
this.clearNotification();
|
|
4246
4992
|
this.iframeManager?.show();
|
|
4247
4993
|
});
|
|
4994
|
+
// 初始化截图管理器(如果启用了截图功能)
|
|
4995
|
+
if (config.screenshot) {
|
|
4996
|
+
// 默认截图目标为 document.body,可以通过配置自定义
|
|
4997
|
+
const targetElement = document.body;
|
|
4998
|
+
this.screenshotManager = new ScreenshotManager(targetElement, config.screenshot);
|
|
4999
|
+
console.log('CustomerSDK screenshot manager initialized');
|
|
5000
|
+
}
|
|
4248
5001
|
this.isInitialized = true;
|
|
4249
5002
|
console.log('CustomerSDK initialized successfully (iframe pre-connected to SSE)');
|
|
4250
5003
|
}
|
|
@@ -4338,14 +5091,38 @@ class CustomerServiceSDK {
|
|
|
4338
5091
|
clearNotification() {
|
|
4339
5092
|
this.iconManager?.clearNotification();
|
|
4340
5093
|
}
|
|
5094
|
+
/**
|
|
5095
|
+
* 设置截图目标元素
|
|
5096
|
+
*/
|
|
5097
|
+
setScreenshotTarget(element) {
|
|
5098
|
+
this.screenshotManager?.setTargetElement(element);
|
|
5099
|
+
}
|
|
5100
|
+
/**
|
|
5101
|
+
* 手动触发截图
|
|
5102
|
+
*/
|
|
5103
|
+
async captureScreenshot() {
|
|
5104
|
+
if (!this.screenshotManager) {
|
|
5105
|
+
console.warn('截图功能未启用');
|
|
5106
|
+
return false;
|
|
5107
|
+
}
|
|
5108
|
+
return await this.screenshotManager.captureOnce();
|
|
5109
|
+
}
|
|
5110
|
+
/**
|
|
5111
|
+
* 获取截图状态
|
|
5112
|
+
*/
|
|
5113
|
+
getScreenshotState() {
|
|
5114
|
+
return this.screenshotManager?.getState() || null;
|
|
5115
|
+
}
|
|
4341
5116
|
/**
|
|
4342
5117
|
* 销毁 SDK
|
|
4343
5118
|
*/
|
|
4344
5119
|
destroy() {
|
|
4345
5120
|
this.iconManager?.hide();
|
|
4346
5121
|
this.iframeManager?.close();
|
|
5122
|
+
this.screenshotManager?.destroy();
|
|
4347
5123
|
this.iconManager = null;
|
|
4348
5124
|
this.iframeManager = null;
|
|
5125
|
+
this.screenshotManager = null;
|
|
4349
5126
|
this.config = null;
|
|
4350
5127
|
this.isInitialized = false;
|
|
4351
5128
|
console.log('CustomerSDK destroyed');
|
|
@@ -4478,6 +5255,21 @@ const destroy = () => {
|
|
|
4478
5255
|
sdk.destroy();
|
|
4479
5256
|
globalSDKInstance = null;
|
|
4480
5257
|
};
|
|
5258
|
+
/**
|
|
5259
|
+
* 截图相关API
|
|
5260
|
+
*/
|
|
5261
|
+
const setScreenshotTarget = (element) => {
|
|
5262
|
+
const sdk = getInstance();
|
|
5263
|
+
sdk.setScreenshotTarget(element);
|
|
5264
|
+
};
|
|
5265
|
+
const captureScreenshot = async () => {
|
|
5266
|
+
const sdk = getInstance();
|
|
5267
|
+
return await sdk.captureScreenshot();
|
|
5268
|
+
};
|
|
5269
|
+
const getScreenshotState = () => {
|
|
5270
|
+
const sdk = getInstance();
|
|
5271
|
+
return sdk.getScreenshotState();
|
|
5272
|
+
};
|
|
4481
5273
|
// 默认导出
|
|
4482
5274
|
var index = {
|
|
4483
5275
|
init,
|
|
@@ -4494,7 +5286,10 @@ var index = {
|
|
|
4494
5286
|
getConnectionStatus,
|
|
4495
5287
|
showNotification,
|
|
4496
5288
|
clearNotification,
|
|
5289
|
+
setScreenshotTarget,
|
|
5290
|
+
captureScreenshot,
|
|
5291
|
+
getScreenshotState,
|
|
4497
5292
|
destroy
|
|
4498
5293
|
};
|
|
4499
5294
|
|
|
4500
|
-
export { CustomerServiceSDK, clearNotification, closeChat, index as default, destroy, getConnectionStatus, getInstance, hideIcon, init, isChatOpen, openChat, sendToIframe, setIconCoordinates, setIconPosition, setIconStyle, showIcon, showNotification };
|
|
5295
|
+
export { CustomerServiceSDK, captureScreenshot, clearNotification, closeChat, index as default, destroy, getConnectionStatus, getInstance, getScreenshotState, hideIcon, init, isChatOpen, openChat, sendToIframe, setIconCoordinates, setIconPosition, setIconStyle, setScreenshotTarget, showIcon, showNotification };
|