chat-video-sdk
Version:
SDK for 1:1 chat and video calling
996 lines (990 loc) • 30 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
Chat: () => Chat,
VideoCall: () => VideoCall
});
module.exports = __toCommonJS(src_exports);
// src/components/Chat.tsx
var import_react = __toESM(require("react"));
var import_socket = require("socket.io-client");
// #style-inject:#style-inject
function styleInject(css, { insertAt } = {}) {
if (!css || typeof document === "undefined")
return;
const head = document.head || document.getElementsByTagName("head")[0];
const style = document.createElement("style");
style.type = "text/css";
if (insertAt === "top") {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
// src/components/styles.css
styleInject(`.chat-container {
display: flex;
flex-direction: column;
height: 100%;
min-height: 400px;
border: 1px solid #ddd;
border-radius: 8px;
}
.messages {
flex: 1;
padding: 16px;
overflow-y: auto;
}
.message {
margin: 8px 0;
padding: 8px 12px;
border-radius: 12px;
max-width: 70%;
}
.message.sent {
background-color: #0084ff;
color: white;
margin-left: auto;
}
.message.received {
background-color: #f0f0f0;
margin-right: auto;
}
.input-area {
display: flex;
padding: 16px;
border-top: 1px solid #ddd;
}
.input-area input {
flex: 1;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 20px;
margin-right: 8px;
}
.input-area button {
padding: 8px 16px;
background-color: #0084ff;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
}
.input-area button:hover {
background-color: #0073e6;
}
.video-call-container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
}
.video-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
margin-bottom: 16px;
}
.video-wrapper {
position: relative;
padding-top: 56.25%;
border-radius: 8px;
overflow: hidden;
}
.video-wrapper video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.video-wrapper span {
position: absolute;
bottom: 8px;
left: 8px;
background-color: rgba(0, 0, 0, 0.6);
color: white;
padding: 4px 8px;
border-radius: 4px;
}
.controls {
display: flex;
justify-content: center;
gap: 16px;
}
.controls button {
padding: 12px 24px;
border-radius: 24px;
border: none;
cursor: pointer;
font-weight: 500;
}
.controls button:first-child {
background-color: #4caf50;
color: white;
}
.controls button:last-child {
background-color: #f44336;
color: white;
}
.chat-sdk-container {
display: flex;
flex-direction: column;
height: 100%;
min-height: 500px;
max-height: 800px;
background-color: #f0f2f5;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
font-family:
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Helvetica,
Arial,
sans-serif;
}
.chat-sdk-header {
display: flex;
align-items: center;
padding: 12px 16px;
background-color: #fff;
border-bottom: 1px solid #e0e0e0;
border-radius: 8px 8px 0 0;
}
.chat-sdk-user-info {
display: flex;
align-items: center;
gap: 12px;
}
.chat-sdk-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.chat-sdk-user-details {
display: flex;
flex-direction: column;
}
.chat-sdk-user-details h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
color: #1a1a1a;
}
.chat-sdk-typing {
font-size: 13px;
color: #06cf9c;
}
.chat-sdk-messages {
flex: 1;
padding: 16px;
overflow-y: auto;
background-color: #efeae2;
background-image: url("data:image/svg+xml,%3Csvg width='64' height='64' viewBox='0 0 64 64' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 16c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm0-2c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zm33.414-6l5.95-5.95L45.95.636 40 6.586 34.05.636 32.636 2.05 38.586 8l-5.95 5.95 1.414 1.414L40 9.414l5.95 5.95 1.414-1.414L41.414 8zM40 48c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm0-2c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zM9.414 40l5.95-5.95-1.414-1.414L8 38.586l-5.95-5.95L.636 34.05 6.586 40l-5.95 5.95 1.414 1.414L8 41.414l5.95 5.95 1.414-1.414L9.414 40z' fill='%239C92AC' fill-opacity='0.05' fill-rule='evenodd'/%3E%3C/svg%3E");
}
.chat-sdk-message {
display: flex;
flex-direction: column;
max-width: 65%;
margin: 4px 0;
padding: 8px 12px;
border-radius: 7.5px;
position: relative;
box-shadow: 0 1px 0.5px rgba(0, 0, 0, 0.13);
}
.chat-sdk-sent {
align-self: flex-end;
background-color: #e7ffdb;
border-top-right-radius: 0;
}
.chat-sdk-received {
align-self: flex-start;
background-color: #ffffff;
border-top-left-radius: 0;
}
.chat-sdk-message-content {
display: flex;
flex-direction: column;
}
.chat-sdk-message-content p {
margin: 0;
font-size: 14px;
line-height: 19px;
color: #111b21;
}
.chat-sdk-message-meta {
display: flex;
align-items: center;
gap: 4px;
margin-top: 2px;
}
.chat-sdk-message-meta time {
font-size: 11px;
color: #667781;
}
.chat-sdk-status {
width: 16px;
height: 11px;
position: relative;
}
.chat-sdk-sent::after {
content: "";
position: absolute;
right: -8px;
top: 0;
width: 0;
height: 0;
border-left: 8px solid #e7ffdb;
border-top: 8px solid transparent;
}
.chat-sdk-received::before {
content: "";
position: absolute;
left: -8px;
top: 0;
width: 0;
height: 0;
border-right: 8px solid #ffffff;
border-top: 8px solid transparent;
}
.chat-sdk-status.chat-sdk-sent::after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='11' fill='%238696a0'%3E%3Cpath d='M10.95 2.5 5.5 8 2.05 4.5l-.84.85L5.5 9.5l6.23-6.23-.78-.77z'/%3E%3C/svg%3E");
}
.chat-sdk-status.chat-sdk-delivered::after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='11' fill='%238696a0'%3E%3Cpath d='m15.01 3.316-.478-.372a.365.365 0 0 0-.51.063L8.666 9.879a.32.32 0 0 1-.484.033l-.358-.325a.32.32 0 0 0-.484.032l-.378.483a.418.418 0 0 0 .036.54l1.32 1.266c.143.14.361.125.484-.033l6.272-8.048a.366.366 0 0 0-.064-.512zm-4.1 0-.478-.372a.365.365 0 0 0-.51.063L4.566 9.879a.32.32 0 0 1-.484.033L1.891 7.769a.366.366 0 0 0-.515.006l-.423.433a.364.364 0 0 0 .006.514l3.258 3.185c.143.14.361.125.484-.033l6.272-8.048a.365.365 0 0 0-.063-.51z'/%3E%3C/svg%3E");
}
.chat-sdk-status.chat-sdk-read::after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='11' fill='%234fc3f7'%3E%3Cpath d='m15.01 3.316-.478-.372a.365.365 0 0 0-.51.063L8.666 9.879a.32.32 0 0 1-.484.033l-.358-.325a.32.32 0 0 0-.484.032l-.378.483a.418.418 0 0 0 .036.54l1.32 1.266c.143.14.361.125.484-.033l6.272-8.048a.366.366 0 0 0-.064-.512zm-4.1 0-.478-.372a.365.365 0 0 0-.51.063L4.566 9.879a.32.32 0 0 1-.484.033L1.891 7.769a.366.366 0 0 0-.515.006l-.423.433a.364.364 0 0 0 .006.514l3.258 3.185c.143.14.361.125.484-.033l6.272-8.048a.365.365 0 0 0-.063-.51z'/%3E%3C/svg%3E");
}
.chat-sdk-image-message {
max-width: 100%;
border-radius: 4px;
margin-bottom: 4px;
}
.chat-sdk-input-area {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background-color: #fff;
border-top: 1px solid #e0e0e0;
border-radius: 0 0 8px 8px;
}
.chat-sdk-input {
flex: 1;
padding: 9px 12px;
border: 1px solid #e0e0e0;
border-radius: 8px;
font-size: 15px;
line-height: 20px;
outline: none;
transition: border-color 0.2s;
}
.chat-sdk-input:focus {
border-color: #00a884;
}
.chat-sdk-send-button {
padding: 8px 12px;
background-color: #00a884;
color: white;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
}
.chat-sdk-send-button:hover {
background-color: #008f72;
}
.chat-sdk-send-button:disabled {
background-color: #85c7bb;
cursor: not-allowed;
}
.chat-sdk-video-container {
display: flex;
flex-direction: column;
height: 100%;
min-height: 500px;
max-height: 800px;
background-color: #f0f2f5;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
font-family:
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Helvetica,
Arial,
sans-serif;
}
.chat-sdk-video-header {
display: flex;
align-items: center;
padding: 12px 16px;
background-color: #075e54;
color: white;
border-radius: 8px 8px 0 0;
}
.chat-sdk-video-grid {
flex: 1;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
padding: 16px;
background-color: #1c1c1c;
}
.chat-sdk-video-wrapper {
position: relative;
padding-top: 56.25%;
background-color: #2c2c2c;
border-radius: 8px;
overflow: hidden;
}
.chat-sdk-video-wrapper video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.chat-sdk-video-wrapper span {
position: absolute;
bottom: 8px;
left: 8px;
background-color: rgba(0, 0, 0, 0.6);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
}
.chat-sdk-video-controls {
display: flex;
justify-content: center;
gap: 16px;
padding: 16px;
background-color: #075e54;
border-radius: 0 0 8px 8px;
}
.chat-sdk-video-button {
padding: 12px 24px;
border-radius: 24px;
border: none;
cursor: pointer;
font-weight: 500;
font-size: 14px;
transition: background-color 0.2s;
}
.chat-sdk-video-button.start {
background-color: #25d366;
color: white;
}
.chat-sdk-video-button.start:hover {
background-color: #20bd5a;
}
.chat-sdk-video-button.start:disabled {
background-color: #92e6b4;
cursor: not-allowed;
}
.chat-sdk-video-button.end {
background-color: #dc3545;
color: white;
}
.chat-sdk-video-button.end:hover {
background-color: #c82333;
}
.chat-sdk-video-button.end:disabled {
background-color: #f1adb4;
cursor: not-allowed;
}
.chat-sdk-call-status {
font-size: 13px;
color: #25d366;
margin-top: 2px;
}
@media (max-width: 900px) {
.video-call-container,
.chat-sdk-container,
.chat-sdk-video-container {
max-width: 100%;
min-height: 350px;
max-height: none;
border-radius: 0;
}
.video-grid,
.chat-sdk-video-grid {
grid-template-columns: 1fr;
gap: 8px;
padding: 8px;
}
}
@media (max-width: 900px) {
.chat-container,
.chat-sdk-container {
max-width: 100%;
min-height: 350px;
max-height: none;
border-radius: 0;
box-shadow: none;
}
.messages,
.chat-sdk-messages {
padding: 8px;
}
}
@media (max-width: 600px) {
.chat-container,
.chat-sdk-container {
border-radius: 0;
min-height: 200px;
max-height: none;
box-shadow: none;
padding: 0;
}
.chat-sdk-header {
padding: 8px 8px;
font-size: 15px;
border-radius: 0;
}
.chat-sdk-user-details h3 {
font-size: 14px;
}
.chat-sdk-messages,
.messages {
padding: 6px;
}
.chat-sdk-message,
.message {
max-width: 95%;
font-size: 13px;
padding: 5px 7px;
margin: 4px 0;
}
.input-area,
.chat-sdk-input-area {
padding: 6px;
gap: 4px;
}
.input-area input,
.chat-sdk-input {
font-size: 13px;
padding: 6px 8px;
}
.chat-sdk-send-button,
.input-area button {
padding: 7px 10px;
font-size: 13px;
}
.chat-sdk-avatar {
width: 28px;
height: 28px;
}
}
@media (max-width: 400px) {
.chat-sdk-header {
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
.chat-sdk-user-info {
gap: 6px;
}
.chat-sdk-user-details h3 {
font-size: 12px;
}
.chat-sdk-message,
.message {
font-size: 12px;
padding: 4px 5px;
}
}
@media (max-width: 600px) {
.chat-container,
.chat-sdk-container,
.chat-sdk-video-container {
border-radius: 0;
min-height: 250px;
max-height: none;
box-shadow: none;
padding: 0;
}
.chat-sdk-header,
.chat-sdk-video-header {
padding: 8px 8px;
font-size: 15px;
border-radius: 0;
}
.chat-sdk-user-details h3 {
font-size: 14px;
}
.chat-sdk-messages,
.messages,
.chat-sdk-video-grid {
padding: 8px;
}
.chat-sdk-message,
.message {
max-width: 90%;
font-size: 13px;
padding: 6px 8px;
}
.input-area,
.chat-sdk-input-area {
padding: 8px;
gap: 4px;
}
.input-area input,
.chat-sdk-input {
font-size: 13px;
padding: 6px 8px;
}
.controls,
.chat-sdk-video-controls {
gap: 8px;
padding: 8px;
}
.controls button,
.chat-sdk-video-button {
padding: 8px 12px;
font-size: 13px;
}
.chat-sdk-avatar {
width: 32px;
height: 32px;
}
}
`);
// src/components/Chat.tsx
var Chat = ({
userId,
receiverId,
serverUrl,
userName = "You",
receiverName = "User",
userAvatar,
receiverAvatar,
onSendMessage,
onMessageReceived,
onTyping,
onError,
customStyles
}) => {
const [messages, setMessages] = (0, import_react.useState)([]);
const [inputMessage, setInputMessage] = (0, import_react.useState)("");
const [isTyping, setIsTyping] = (0, import_react.useState)(false);
const [socket, setSocket] = (0, import_react.useState)(null);
const [isConnected, setIsConnected] = (0, import_react.useState)(false);
const messagesEndRef = (0, import_react.useRef)(null);
const typingTimeoutRef = (0, import_react.useRef)(null);
const processedMessages = (0, import_react.useRef)(/* @__PURE__ */ new Set());
const scrollToBottom = (0, import_react.useCallback)(() => {
var _a;
(_a = messagesEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
}, []);
(0, import_react.useEffect)(() => {
const newSocket = (0, import_socket.io)(serverUrl, {
query: { userId },
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1e3
});
newSocket.on("connect", () => {
console.log("Connected to server");
setIsConnected(true);
});
newSocket.on("disconnect", () => {
console.log("Disconnected from server");
setIsConnected(false);
});
newSocket.on("message", (message) => {
if (!processedMessages.current.has(message.id)) {
processedMessages.current.add(message.id);
setMessages((prev) => [...prev, message]);
onMessageReceived == null ? void 0 : onMessageReceived(message);
scrollToBottom();
if (message.senderId === receiverId) {
newSocket.emit("message_delivered", {
messageId: message.id,
senderId: message.senderId,
receiverId: userId
});
}
}
});
newSocket.on("message_delivered", ({ messageId }) => {
setMessages(
(prev) => prev.map(
(msg) => msg.id === messageId ? { ...msg, status: "delivered" } : msg
)
);
});
newSocket.on("message_read", ({ messageId }) => {
setMessages(
(prev) => prev.map(
(msg) => msg.id === messageId ? { ...msg, status: "read" } : msg
)
);
});
newSocket.on("typing", ({ userId: typingUserId, isTyping: isTyping2 }) => {
if (typingUserId === receiverId) {
setIsTyping(isTyping2);
}
});
newSocket.on("error", (error) => {
console.error("Socket error:", error);
onError == null ? void 0 : onError(error);
});
fetch(`${serverUrl}/chat/history/${userId}/${receiverId}`).then((res) => res.json()).then((data) => {
setMessages(data);
data.forEach((msg) => {
processedMessages.current.add(msg.id);
});
scrollToBottom();
data.filter((msg) => msg.senderId === receiverId).forEach((msg) => {
newSocket.emit("message_read", {
messageId: msg.id,
senderId: msg.senderId,
receiverId: userId
});
});
}).catch((error) => onError == null ? void 0 : onError(error));
setSocket(newSocket);
return () => {
newSocket.disconnect();
};
}, [userId, receiverId, serverUrl, onMessageReceived, onError, scrollToBottom]);
const handleTyping = (0, import_react.useCallback)(() => {
if (socket == null ? void 0 : socket.connected) {
socket.emit("typing", { receiverId, isTyping: true });
onTyping == null ? void 0 : onTyping(true);
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current);
}
typingTimeoutRef.current = setTimeout(() => {
socket.emit("typing", { receiverId, isTyping: false });
onTyping == null ? void 0 : onTyping(false);
}, 1e3);
}
}, [socket, receiverId, onTyping]);
const handleSendMessage = (0, import_react.useCallback)(async () => {
if (!inputMessage.trim() || !(socket == null ? void 0 : socket.connected))
return;
const message = {
id: `${userId}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
content: inputMessage.trim(),
senderId: userId,
receiverId,
timestamp: Date.now(),
status: "sent",
type: "text"
};
try {
setMessages((prev) => [...prev, message]);
processedMessages.current.add(message.id);
scrollToBottom();
socket.emit("send_message", message);
setInputMessage("");
onSendMessage == null ? void 0 : onSendMessage(message);
} catch (error) {
setMessages((prev) => prev.filter((msg) => msg.id !== message.id));
processedMessages.current.delete(message.id);
onError == null ? void 0 : onError(error);
}
}, [inputMessage, socket, userId, receiverId, onSendMessage, onError, scrollToBottom]);
const formatTime = (timestamp) => {
return new Date(timestamp).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit"
});
};
return /* @__PURE__ */ import_react.default.createElement("div", { className: "chat-sdk-container", style: customStyles == null ? void 0 : customStyles.container }, /* @__PURE__ */ import_react.default.createElement("div", { className: "chat-sdk-header", style: customStyles == null ? void 0 : customStyles.header }, /* @__PURE__ */ import_react.default.createElement("div", { className: "chat-sdk-user-info" }, receiverAvatar && /* @__PURE__ */ import_react.default.createElement(
"img",
{
src: receiverAvatar,
alt: receiverName,
className: "chat-sdk-avatar"
}
), /* @__PURE__ */ import_react.default.createElement("div", { className: "chat-sdk-user-details" }, /* @__PURE__ */ import_react.default.createElement("h3", null, receiverName), isTyping && /* @__PURE__ */ import_react.default.createElement("span", { className: "chat-sdk-typing" }, "typing...")))), /* @__PURE__ */ import_react.default.createElement("div", { className: "chat-sdk-messages", style: customStyles == null ? void 0 : customStyles.messageList }, messages.map((message) => {
var _a, _b;
return /* @__PURE__ */ import_react.default.createElement(
"div",
{
key: message.id,
className: `chat-sdk-message ${message.senderId === userId ? "chat-sdk-sent" : "chat-sdk-received"}`,
style: message.senderId === userId ? (_a = customStyles == null ? void 0 : customStyles.messageBubble) == null ? void 0 : _a.sent : (_b = customStyles == null ? void 0 : customStyles.messageBubble) == null ? void 0 : _b.received
},
message.type === "image" && message.fileUrl && /* @__PURE__ */ import_react.default.createElement(
"img",
{
src: message.fileUrl,
alt: message.fileName || "Image",
className: "chat-sdk-image-message"
}
),
/* @__PURE__ */ import_react.default.createElement("div", { className: "chat-sdk-message-content" }, /* @__PURE__ */ import_react.default.createElement("p", null, message.content), /* @__PURE__ */ import_react.default.createElement("div", { className: "chat-sdk-message-meta" }, /* @__PURE__ */ import_react.default.createElement("time", null, formatTime(message.timestamp)), message.senderId === userId && /* @__PURE__ */ import_react.default.createElement("span", { className: `chat-sdk-status chat-sdk-${message.status}` })))
);
}), /* @__PURE__ */ import_react.default.createElement("div", { ref: messagesEndRef })), /* @__PURE__ */ import_react.default.createElement("div", { className: "chat-sdk-input-area", style: customStyles == null ? void 0 : customStyles.inputArea }, /* @__PURE__ */ import_react.default.createElement(
"input",
{
type: "text",
value: inputMessage,
onChange: (e) => {
setInputMessage(e.target.value);
handleTyping();
},
onKeyPress: (e) => e.key === "Enter" && handleSendMessage(),
placeholder: "Type a message...",
className: "chat-sdk-input"
}
), /* @__PURE__ */ import_react.default.createElement(
"button",
{
onClick: handleSendMessage,
className: "chat-sdk-send-button",
style: customStyles == null ? void 0 : customStyles.button,
disabled: !inputMessage.trim()
},
"Bhej do"
)));
};
// src/components/VideoCall.tsx
var import_react2 = __toESM(require("react"));
var import_socket2 = require("socket.io-client");
var import_webrtc_adapter = require("webrtc-adapter");
var VideoCall = ({
userId,
receiverId,
serverUrl,
userName = "You",
receiverName = "User",
userAvatar,
receiverAvatar,
onCallReceived,
onCallEnded,
onCallStarted,
onError
}) => {
const [socket, setSocket] = (0, import_react2.useState)(null);
const peerConnection = (0, import_react2.useRef)(null);
const localVideoRef = (0, import_react2.useRef)(null);
const remoteVideoRef = (0, import_react2.useRef)(null);
const localStream = (0, import_react2.useRef)(null);
const [isCalling, setIsCalling] = (0, import_react2.useState)(false);
(0, import_react2.useEffect)(() => {
const newSocket = (0, import_socket2.io)(serverUrl);
newSocket.on("connect", () => {
newSocket.emit("register", userId);
});
newSocket.on("call-received", (callerId) => {
onCallReceived == null ? void 0 : onCallReceived(callerId);
});
newSocket.on("call-ended", () => {
handleEndCall();
onCallEnded == null ? void 0 : onCallEnded();
});
newSocket.on("offer", async ({ offer, callerId }) => {
try {
await handleOffer(offer, callerId);
} catch (error) {
onError == null ? void 0 : onError(error);
}
});
newSocket.on("answer", async ({ answer }) => {
var _a;
try {
await ((_a = peerConnection.current) == null ? void 0 : _a.setRemoteDescription(
new RTCSessionDescription(answer)
));
} catch (error) {
onError == null ? void 0 : onError(error);
}
});
newSocket.on("ice-candidate", async ({ candidate }) => {
var _a;
try {
await ((_a = peerConnection.current) == null ? void 0 : _a.addIceCandidate(
new RTCIceCandidate(candidate)
));
} catch (error) {
onError == null ? void 0 : onError(error);
}
});
setSocket(newSocket);
return () => {
handleEndCall();
newSocket.disconnect();
};
}, [userId, serverUrl, onCallReceived, onCallEnded, onError]);
const setupPeerConnection = (0, import_react2.useCallback)(async () => {
peerConnection.current = new RTCPeerConnection({
iceServers: [
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "stun:stun1.l.google.com:19302" }
]
});
peerConnection.current.onicecandidate = (event) => {
if (event.candidate) {
socket == null ? void 0 : socket.emit("ice-candidate", { candidate: event.candidate });
}
};
peerConnection.current.ontrack = (event) => {
if (remoteVideoRef.current) {
remoteVideoRef.current.srcObject = event.streams[0];
}
};
if (localStream.current) {
localStream.current.getTracks().forEach((track) => {
var _a;
(_a = peerConnection.current) == null ? void 0 : _a.addTrack(track, localStream.current);
});
}
}, [socket]);
const handleStartCall = async () => {
try {
localStream.current = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
if (localVideoRef.current) {
localVideoRef.current.srcObject = localStream.current;
}
await setupPeerConnection();
const offer = await peerConnection.current.createOffer();
await peerConnection.current.setLocalDescription(offer);
socket == null ? void 0 : socket.emit("call-user", {
receiverId,
offer
});
setIsCalling(true);
onCallStarted == null ? void 0 : onCallStarted();
} catch (error) {
onError == null ? void 0 : onError(error);
}
};
const handleOffer = async (offer, callerId) => {
try {
localStream.current = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
if (localVideoRef.current) {
localVideoRef.current.srcObject = localStream.current;
}
await setupPeerConnection();
await peerConnection.current.setRemoteDescription(
new RTCSessionDescription(offer)
);
const answer = await peerConnection.current.createAnswer();
await peerConnection.current.setLocalDescription(answer);
socket == null ? void 0 : socket.emit("answer", {
callerId,
answer
});
setIsCalling(true);
} catch (error) {
onError == null ? void 0 : onError(error);
}
};
const handleEndCall = () => {
var _a, _b;
(_a = localStream.current) == null ? void 0 : _a.getTracks().forEach((track) => track.stop());
(_b = peerConnection.current) == null ? void 0 : _b.close();
socket == null ? void 0 : socket.emit("end-call");
if (localVideoRef.current) {
localVideoRef.current.srcObject = null;
}
if (remoteVideoRef.current) {
remoteVideoRef.current.srcObject = null;
}
setIsCalling(false);
localStream.current = null;
peerConnection.current = null;
};
return /* @__PURE__ */ import_react2.default.createElement("div", { className: "chat-sdk-video-container" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "chat-sdk-video-header" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "chat-sdk-user-info" }, receiverAvatar && /* @__PURE__ */ import_react2.default.createElement(
"img",
{
src: receiverAvatar,
alt: receiverName,
className: "chat-sdk-avatar"
}
), /* @__PURE__ */ import_react2.default.createElement("div", { className: "chat-sdk-user-details" }, /* @__PURE__ */ import_react2.default.createElement("h3", null, receiverName), isCalling && /* @__PURE__ */ import_react2.default.createElement("span", { className: "chat-sdk-call-status" }, "On call")))), /* @__PURE__ */ import_react2.default.createElement("div", { className: "chat-sdk-video-grid" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "chat-sdk-video-wrapper local" }, /* @__PURE__ */ import_react2.default.createElement(
"video",
{
ref: localVideoRef,
autoPlay: true,
playsInline: true,
muted: true
}
), /* @__PURE__ */ import_react2.default.createElement("span", null, userName)), /* @__PURE__ */ import_react2.default.createElement("div", { className: "chat-sdk-video-wrapper remote" }, /* @__PURE__ */ import_react2.default.createElement(
"video",
{
ref: remoteVideoRef,
autoPlay: true,
playsInline: true
}
), /* @__PURE__ */ import_react2.default.createElement("span", null, receiverName))), /* @__PURE__ */ import_react2.default.createElement("div", { className: "chat-sdk-video-controls" }, /* @__PURE__ */ import_react2.default.createElement(
"button",
{
onClick: handleStartCall,
className: "chat-sdk-video-button start",
disabled: isCalling
},
"Start Call"
), /* @__PURE__ */ import_react2.default.createElement(
"button",
{
onClick: handleEndCall,
className: "chat-sdk-video-button end",
disabled: !isCalling
},
"End Call"
)));
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Chat,
VideoCall
});
//# sourceMappingURL=index.js.map