UNPKG

e2ee-chat

Version:

A simple realtime chat SDK for web and mobile apps using socket.io with support for end-to-end encryption and multi-tenant backend integration.

221 lines (186 loc) 5.99 kB
const { io } = require("socket.io-client"); const axios = require("axios"); let socket = null; let presenceCallbacks = new Set(); let messageSeenCallbacks = new Set(); function connectSocket({ serverUrl, roomId, userId, userType, onJoin, onMessage, onHandlerChanged, onPresenceChange, onMessageSeen, apiKey, }) { console.log("Initializing socket connection to:", serverUrl); // Clean the server URL by removing any query parameters const cleanUrl = serverUrl.split("?")[0]; socket = io(cleanUrl, { reconnection: true, reconnectionAttempts: 5, reconnectionDelay: 1000, query: { apiKey }, transports: ["polling"], // Use polling only for better HTTPS compatibility timeout: 20000, // Increase timeout to 20 seconds forceNew: true, // Force a new connection path: "/socket.io/", // Explicitly set the Socket.IO path }); socket.on("connect", () => { console.log("Socket connected, joining room:", { roomId, userId, userType, }); socket.emit("joinRoom", { roomId, userId, userType, apiKey }); // Emit user online status socket.emit("user:online", userId); }); socket.on("connect_error", (error) => { console.error("Socket connection error:", error.message); }); socket.on("error", (error) => { console.error("Socket error:", error); }); socket.on("joinedRoom", (data) => { console.log("Successfully joined room:", data); onJoin && onJoin(data); }); socket.on("receiveMessage", (msg) => { console.log("Received message:", msg); onMessage && onMessage(msg); }); socket.on("handlerChanged", (payload) => { console.log("Handler changed:", payload); onHandlerChanged && onHandlerChanged(payload); }); socket.on("unauthorized", (msg) => { console.error("Socket unauthorized:", msg); }); // Add presence change handler socket.on("presence:change", (data) => { console.log("Presence change:", data); presenceCallbacks.forEach((callback) => callback(data)); onPresenceChange && onPresenceChange(data); }); // Add message seen handler socket.on("message:seen", (data) => { console.log("Message seen:", data); messageSeenCallbacks.forEach((callback) => callback(data)); onMessageSeen && onMessageSeen(data); }); socket.on("disconnect", (reason) => { console.log("Socket disconnected:", reason); if (reason === "io server disconnect") { // Server initiated disconnect, try to reconnect socket.connect(); } }); // Add ping/pong handlers to monitor connection health socket.on("ping", () => { console.log("Ping sent"); }); socket.on("pong", (latency) => { console.log("Pong received, latency:", latency); }); } function sendMessage({ roomId, senderId, receiverId, encryptedText, apiKey }) { if (!socket) return; socket.emit("sendMessage", { roomId, senderId, receiverId, encryptedText, apiKey, }); } function requestTakeover({ roomId, newHandlerId, apiKey }) { if (!socket) return; socket.emit("takeover", { roomId, newHandlerId, apiKey }); } function disconnectSocket() { if (socket) socket.disconnect(); } function markMessageAsSeen({ messageId, userId, roomId, apiKey }) { if (!socket) return; socket.emit("message:seen", { messageId, userId, roomId, apiKey }); } function subscribeToPresence(callback) { presenceCallbacks.add(callback); return () => presenceCallbacks.delete(callback); } function subscribeToMessageSeen(callback) { messageSeenCallbacks.add(callback); return () => messageSeenCallbacks.delete(callback); } function markMessagesAsRead({ roomId, userId, apiKey }) { if (!socket) return; socket.emit("markMessagesAsRead", { roomId, userId, apiKey }); } // async function getUnreadCounts(serverUrl, userId, apiKey) { // const url = `${serverUrl}/unread-counts/${userId}?apiKey=${apiKey}`; // const response = await axios.get(url); // return response.data; // } // // In socket.js, update the getUnreadCounts function: // async function getUnreadCounts(serverUrl, userId, apiKey) { // try { // const cleanUrl = serverUrl.split("?")[0]; // const url = `${cleanUrl}/unread-counts/${userId}?apiKey=${apiKey}`; // console.log("SDK: Fetching unread counts from:", url); // const response = await axios.get(url); // console.log("SDK: Raw unread counts response:", response.data); // // The server should return data in this format: // // { counts: { roomId1: 2, roomId2: 1, ... } } // if (response.data && response.data.counts) { // return response.data.counts; // } // // If the server returns a different format, handle it here // if (response.data && typeof response.data === "object") { // return response.data; // } // console.warn( // "SDK: Unexpected unread counts response format:", // response.data // ); // return {}; // } catch (error) { // console.error("SDK: Failed to fetch unread counts:", error); // return {}; // } // } async function getUnreadCounts(serverUrl, userId, apiKey) { try { // Add validation for required parameters if (!serverUrl || !userId || !apiKey) { console.error("SDK: Missing required parameters:", { serverUrl, userId, apiKey, }); return {}; } const cleanUrl = serverUrl.split("?")[0]; const url = `${cleanUrl}/unread-counts/${userId}?apiKey=${apiKey}`; console.log("SDK: Fetching unread counts from:", url); const response = await axios.get(url); console.log("SDK: Raw unread counts response:", response.data); return response.data || {}; } catch (error) { console.error("SDK: Failed to fetch unread counts:", error); return {}; } } module.exports = { connectSocket, sendMessage, requestTakeover, disconnectSocket, markMessageAsSeen, subscribeToPresence, subscribeToMessageSeen, markMessagesAsRead, getUnreadCounts, };