livechat-widget
Version:
LiveChat Widget for Next.js applications
310 lines (309 loc) • 20.4 kB
JavaScript
"use client";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect, useRef } from "react";
import MessageList from "./MessageList";
import MessageInput from "./MessageInput";
import { getWebSocketService, setWebSocketUrl, } from "../../lib/websocket";
import { fetchMessages, getOrCreateRoom, setApiBaseUrl } from "../../lib/api";
import Image from "next/image";
// เพิ่มตัวแปร global เพื่อป้องกันการสร้าง connection ซ้ำซ้อน
var isInitializingChat = false;
var lastDisconnectTime = 0;
var reconnectLock = false;
var MOCK_USER_INFO = {
username: "Guest User",
profile_image: "/image/profile.png",
};
var generateUserCode = function () {
return "user_" + Math.random().toString(36).substring(2, 15);
};
var ChatWidget = function (_a) {
var roomCode = _a.roomCode, appId = _a.appId, _b = _a.roomName, roomName = _b === void 0 ? "Chat Room" : _b, _c = _a.theme, theme = _c === void 0 ? "light" : _c, bgColor = _a.bgColor, bgColorClass = _a.bgColorClass, textColor = _a.textColor, textColorClass = _a.textColorClass, bgInput = _a.bgInput, bgInputClass = _a.bgInputClass, textInputColor = _a.textInputColor, textInputColorClass = _a.textInputColorClass, currentUserColor = _a.currentUserColor, currentUserColorClass = _a.currentUserColorClass, adminColor = _a.adminColor, adminColorClass = _a.adminColorClass, buttonBgColor = _a.buttonBgColor, buttonBgClass = _a.buttonBgClass, externalUserInfo = _a.userInfo, externalUserCode = _a.userCode, width = _a.width, height = _a.height, apiUrl = _a.apiUrl, wsUrl = _a.wsUrl;
var _d = useState([]), messages = _d[0], setMessages = _d[1];
var userInfo = useState(externalUserInfo || MOCK_USER_INFO)[0];
var _e = useState(true), isLoading = _e[0], setIsLoading = _e[1];
var _f = useState(false), isConnected = _f[0], setIsConnected = _f[1];
var _g = useState(null), error = _g[0], setError = _g[1];
var _h = useState(null), wsServiceInstance = _h[0], setWsServiceInstance = _h[1];
// เพิ่ม useRef เพื่อเก็บค่า dependencies เดิม
var prevDepsRef = useRef({});
useEffect(function () {
if (apiUrl) {
setApiBaseUrl(apiUrl);
}
if (wsUrl) {
setWebSocketUrl(wsUrl);
}
// ตรวจสอบว่ามีการเปลี่ยนแปลง dependencies จริงๆ หรือไม่
var depsChanged = prevDepsRef.current.roomCode !== roomCode ||
prevDepsRef.current.appId !== appId ||
prevDepsRef.current.userCode !== externalUserCode ||
prevDepsRef.current.apiUrl !== apiUrl ||
prevDepsRef.current.wsUrl !== wsUrl;
// อัพเดต ref เพื่อเก็บค่า dependencies ปัจจุบัน
prevDepsRef.current = {
roomCode: roomCode,
appId: appId,
userCode: externalUserCode,
apiUrl: apiUrl,
wsUrl: wsUrl,
};
// ถ้ามีการเปลี่ยนแปลง dependencies จริงๆ จึงเรียก initChat
if (depsChanged) {
console.log("Dependencies changed, initializing chat");
var wsService_1;
var initChat = function () { return __awaiter(void 0, void 0, void 0, function () {
var previousMessages, currentUserCode, currentRoomCode, hasUserCodeChanged, hasRoomCodeChanged, err_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// ตรวจสอบว่ากำลังมีการเชื่อมต่ออยู่หรือไม่
// ตั้งค่าตัวแปร global เพื่อป้องกันการสร้าง connection ซ้ำซ้อน
isInitializingChat = true;
_a.label = 1;
case 1:
_a.trys.push([1, 13, 14, 15]);
if (!roomCode || !appId || !externalUserCode) {
setError("Room code, app ID, and user code are required");
isInitializingChat = false;
return [2 /*return*/];
}
setIsLoading(true);
return [4 /*yield*/, getOrCreateRoom(roomCode, appId, roomName)];
case 2:
_a.sent();
return [4 /*yield*/, fetchMessages(roomCode, appId)];
case 3:
previousMessages = _a.sent();
setMessages(previousMessages || []);
if (!wsServiceInstance) return [3 /*break*/, 10];
console.log("Existing WebSocket connection found");
currentUserCode = wsServiceInstance.getCurrentUserCode();
currentRoomCode = wsServiceInstance.getCurrentRoomCode();
hasUserCodeChanged = currentUserCode !== externalUserCode;
hasRoomCodeChanged = currentRoomCode !== roomCode;
if (!(hasUserCodeChanged || hasRoomCodeChanged)) return [3 /*break*/, 6];
wsServiceInstance.disconnect();
// รอให้การเชื่อมต่อเดิมปิดสมบูรณ์ก่อนสร้างการเชื่อมต่อใหม่
// เพิ่มเวลารอเป็น 1000ms (1 วินาที)
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 1000); })];
case 4:
// รอให้การเชื่อมต่อเดิมปิดสมบูรณ์ก่อนสร้างการเชื่อมต่อใหม่
// เพิ่มเวลารอเป็น 1000ms (1 วินาที)
_a.sent();
// สร้าง WebSocket service ใหม่
wsService_1 = getWebSocketService();
setWsServiceInstance(wsService_1);
return [4 /*yield*/, wsService_1.connect(roomCode, appId, externalUserCode)];
case 5:
_a.sent();
return [3 /*break*/, 9];
case 6:
console.log("Using existing WebSocket connection");
if (!!wsServiceInstance.isConnected()) return [3 /*break*/, 8];
return [4 /*yield*/, wsServiceInstance.connect(roomCode, appId, externalUserCode)];
case 7:
_a.sent();
_a.label = 8;
case 8:
wsService_1 = wsServiceInstance;
_a.label = 9;
case 9: return [3 /*break*/, 12];
case 10:
wsService_1 = getWebSocketService();
setWsServiceInstance(wsService_1);
return [4 /*yield*/, wsService_1.connect(roomCode, appId, externalUserCode)];
case 11:
_a.sent();
_a.label = 12;
case 12:
// ใช้ isConnected() เพื่อตรวจสอบสถานะการเชื่อมต่อปัจจุบัน
setIsConnected(wsService_1.isConnected());
setIsLoading(false);
setError(null);
return [3 /*break*/, 15];
case 13:
err_1 = _a.sent();
console.error("Chat initialization error:", err_1);
setError("ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้");
setIsLoading(false);
return [3 /*break*/, 15];
case 14:
// รีเซ็ตตัวแปร global เมื่อเสร็จสิ้นการเชื่อมต่อ
isInitializingChat = false;
return [7 /*endfinally*/];
case 15: return [2 /*return*/];
}
});
}); };
initChat();
}
// Cleanup function ที่จะทำงานเมื่อ component unmount หรือเมื่อ dependencies เปลี่ยน
return function () {
// ไม่ต้องปิด WebSocket connection เมื่อเพียงแค่ dependencies เปลี่ยน
// เพราะเราจะจัดการเองใน initChat
console.log("useEffect cleanup called");
};
}, [
roomCode,
appId,
roomName,
externalUserCode,
externalUserInfo,
apiUrl,
wsUrl,
]);
// ฟังก์ชันสำหรับจัดการข้อความใหม่
var handleNewMessage = function (message) {
setMessages(function (prevMessages) {
var isDuplicate = prevMessages.some(function (msg) {
return msg.user_code === message.user_code &&
msg.content === message.content &&
msg.created_at === message.created_at;
});
if (isDuplicate) {
return prevMessages;
}
return __spreadArray(__spreadArray([], prevMessages, true), [message], false);
});
};
// ฟังก์ชันสำหรับจัดการการเปลี่ยนแปลงการเชื่อมต่อ
var handleConnectionChange = function (connected) {
console.log("Connection status:", connected);
setIsConnected(connected);
};
// ฟังก์ชันสำหรับจัดการการลบข้อความ
var handleDeleteMessage = function (messageId) {
console.log("Deleting message:", messageId);
// ค้นหาข้อความที่จะลบ
setMessages(function (prevMessages) {
var messageToDelete = prevMessages.find(function (message) {
return message.message_id === messageId || message.id === messageId;
});
if (messageToDelete) {
// ทำเครื่องหมายว่าข้อความถูกลบแล้ว แต่ยังไม่ลบออกจากรายการ
return prevMessages.map(function (message) {
if (message.message_id === messageId || message.id === messageId) {
return __assign(__assign({}, message), { is_deleted: true, content: "ข้อความถูกลบแล้ว" });
}
return message;
});
}
return prevMessages;
});
// หลังจาก 3 วินาที ให้ลบข้อความออกจากรายการ
setTimeout(function () {
setMessages(function (prevMessages) {
return prevMessages.filter(function (message) {
return message.message_id !== messageId &&
// กรณีข้อความเก่าที่อาจไม่มี message_id
message.id !== messageId;
});
});
}, 3000); // 3 วินาที
};
// จัดการ WebSocket listeners
useEffect(function () {
if (!wsServiceInstance)
return;
// เพิ่ม listeners
wsServiceInstance.addMessageListener(handleNewMessage);
wsServiceInstance.addConnectionListener(handleConnectionChange);
wsServiceInstance.addDeleteMessageListener(handleDeleteMessage);
// ลบ listeners เมื่อ component unmount
return function () {
wsServiceInstance.removeMessageListener(handleNewMessage);
wsServiceInstance.removeConnectionListener(handleConnectionChange);
wsServiceInstance.removeDeleteMessageListener(handleDeleteMessage);
};
}, [wsServiceInstance]);
var handleSendMessage = function (content) {
if (!content.trim())
return;
var newMessage = {
id: "".concat(Date.now()),
room_code: roomCode,
app_id: appId,
user_code: externalUserCode || "anonymous",
user_info: userInfo,
content: content,
created_at: new Date().toISOString(),
};
// ใช้ wsServiceInstance ที่เก็บไว้แทนที่จะเรียก getWebSocketService() ใหม่ทุกครั้ง
if (wsServiceInstance) {
wsServiceInstance.sendMessage(newMessage);
}
else {
console.error("WebSocket service is not initialized");
}
};
return (_jsxs("div", { className: "flex flex-col ".concat(bgColorClass ? "".concat(bgColorClass) : "", " ").concat(textColorClass ? "".concat(textColorClass) : ""), style: {
backgroundColor: bgColor || (theme === "dark" ? "#000000" : "#ffffff"),
color: textColor || (theme === "dark" ? "#ffffff" : "#000000"),
width: typeof width === "number" ? "".concat(width, "px") : width || "100%",
height: typeof height === "number" ? "".concat(height, "px") : height || "100%",
}, children: [_jsxs("div", { className: "flex justify-between items-center p-2 border-b ".concat(bgColorClass ? "".concat(bgColorClass) : bgColor ? "" : "bg-gray-200"), style: {
borderColor: theme === "dark" ? "#333333" : "#e5e7eb",
}, children: [_jsxs("div", { className: "flex items-center", children: [_jsx("span", { className: "font-medium ".concat(textColor ? "" : theme === "dark" ? "text-white" : "text-gray-800"), style: textColor ? { color: textColor } : {}, children: roomName }), _jsx("div", { className: "ml-2 w-2 h-2 rounded-full ".concat(isConnected ? "bg-green-500" : "bg-red-500") })] }), _jsxs("div", { className: "flex items-center", children: [_jsx("span", { className: "text-sm ".concat(textColor
? ""
: theme === "dark"
? "text-gray-300"
: "text-gray-500", " mr-2"), style: textColor ? { color: textColor } : {}, children: userInfo.username }), _jsx("div", { className: "w-8 h-8 rounded-full overflow-hidden", children: _jsx(Image, { src: userInfo.profile_image, alt: userInfo.username, className: "object-cover", width: 32, height: 32 }) })] })] }), isLoading ? (_jsx("div", { className: "flex-1 flex items-center justify-center", children: _jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" }) })) : error ? (_jsx("div", { className: "flex-1 flex items-center justify-center p-4", children: _jsx("div", { className: "text-red-500 text-center ".concat(theme === "dark" ? "text-red-300" : ""), children: error }) })) : (_jsx("div", { className: "flex-1 overflow-y-auto", children: _jsx(MessageList, { messages: messages, currentUserCode: externalUserCode, theme: theme, textColor: textColor, textColorClass: textColorClass, bgColor: bgColor, bgColorClass: bgColorClass, currentUserColor: currentUserColor, currentUserColorClass: currentUserColorClass, adminColor: adminColor, adminColorClass: adminColorClass }) })), _jsx(MessageInput, { onSendMessage: handleSendMessage, isConnected: isConnected, theme: theme, bgInput: bgInput, bgInputClass: bgInputClass, textInputColor: textInputColor, textInputColorClass: textInputColorClass, bgColor: bgColor, bgColorClass: bgColorClass, textColor: textColor, textColorClass: textColorClass, buttonBgColor: buttonBgColor, buttonBgClass: buttonBgClass, userRole: userInfo === null || userInfo === void 0 ? void 0 : userInfo.role })] }));
};
export default ChatWidget;