From 387306cf12aa9217852ebf283bee5b8fec719042 Mon Sep 17 00:00:00 2001 From: qi Date: Fri, 22 Dec 2023 22:23:19 -0500 Subject: [PATCH 1/2] Added an example of implementing private messaging in Vite+React --- .../react-private-messaging/.eslintrc.cjs | 20 ++ examples/react-private-messaging/.gitignore | 24 +++ examples/react-private-messaging/LICENSE | 21 ++ examples/react-private-messaging/README.md | 20 ++ examples/react-private-messaging/index.html | 12 ++ examples/react-private-messaging/package.json | 31 +++ .../react-private-messaging/server/cluster.js | 31 +++ .../server/docker-compose.yml | 7 + .../react-private-messaging/server/index.js | 125 ++++++++++++ .../server/messageStore.js | 54 ++++++ .../server/package.json | 17 ++ .../server/sessionStore.js | 89 +++++++++ examples/react-private-messaging/src/App.css | 179 ++++++++++++++++++ examples/react-private-messaging/src/App.jsx | 141 ++++++++++++++ examples/react-private-messaging/src/Chat.jsx | 111 +++++++++++ .../react-private-messaging/src/SignIn.jsx | 91 +++++++++ examples/react-private-messaging/src/main.jsx | 9 + .../react-private-messaging/src/socket.js | 10 + .../react-private-messaging/vite.config.js | 10 + 19 files changed, 1002 insertions(+) create mode 100644 examples/react-private-messaging/.eslintrc.cjs create mode 100644 examples/react-private-messaging/.gitignore create mode 100644 examples/react-private-messaging/LICENSE create mode 100644 examples/react-private-messaging/README.md create mode 100644 examples/react-private-messaging/index.html create mode 100644 examples/react-private-messaging/package.json create mode 100644 examples/react-private-messaging/server/cluster.js create mode 100644 examples/react-private-messaging/server/docker-compose.yml create mode 100644 examples/react-private-messaging/server/index.js create mode 100644 examples/react-private-messaging/server/messageStore.js create mode 100644 examples/react-private-messaging/server/package.json create mode 100644 examples/react-private-messaging/server/sessionStore.js create mode 100644 examples/react-private-messaging/src/App.css create mode 100644 examples/react-private-messaging/src/App.jsx create mode 100644 examples/react-private-messaging/src/Chat.jsx create mode 100644 examples/react-private-messaging/src/SignIn.jsx create mode 100644 examples/react-private-messaging/src/main.jsx create mode 100644 examples/react-private-messaging/src/socket.js create mode 100644 examples/react-private-messaging/vite.config.js diff --git a/examples/react-private-messaging/.eslintrc.cjs b/examples/react-private-messaging/.eslintrc.cjs new file mode 100644 index 0000000000..4dcb43901a --- /dev/null +++ b/examples/react-private-messaging/.eslintrc.cjs @@ -0,0 +1,20 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react/jsx-runtime', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, + settings: { react: { version: '18.2' } }, + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/examples/react-private-messaging/.gitignore b/examples/react-private-messaging/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/examples/react-private-messaging/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/react-private-messaging/LICENSE b/examples/react-private-messaging/LICENSE new file mode 100644 index 0000000000..190bfbe0ea --- /dev/null +++ b/examples/react-private-messaging/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 qzhang1 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/react-private-messaging/README.md b/examples/react-private-messaging/README.md new file mode 100644 index 0000000000..ac80695fc4 --- /dev/null +++ b/examples/react-private-messaging/README.md @@ -0,0 +1,20 @@ +# Private Message in React + Vite + +This is an implementation of the Private messaging example in React following +Socket.io recommendations https://socket.io/how-to/use-with-react + +## Running the frontend + +``` +npm install +npm run dev +``` + +### Running the server + +``` +cd server +npm install +npm start +``` + diff --git a/examples/react-private-messaging/index.html b/examples/react-private-messaging/index.html new file mode 100644 index 0000000000..c49a808a7b --- /dev/null +++ b/examples/react-private-messaging/index.html @@ -0,0 +1,12 @@ + + + + + + React Socket.io + + +
+ + + diff --git a/examples/react-private-messaging/package.json b/examples/react-private-messaging/package.json new file mode 100644 index 0000000000..58d66c08ab --- /dev/null +++ b/examples/react-private-messaging/package.json @@ -0,0 +1,31 @@ +{ + "name": "basic-crud-socket-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.15.0", + "@mui/material": "^5.15.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "socket.io-client": "^4.7.2" + }, + "devDependencies": { + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.55.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "vite": "^5.0.8" + } +} diff --git a/examples/react-private-messaging/server/cluster.js b/examples/react-private-messaging/server/cluster.js new file mode 100644 index 0000000000..e3bad09f8c --- /dev/null +++ b/examples/react-private-messaging/server/cluster.js @@ -0,0 +1,31 @@ +const cluster = require("cluster"); +const http = require("http"); +const { setupMaster } = require("@socket.io/sticky"); + +const WORKERS_COUNT = 4; + +if (cluster.isMaster) { + console.log(`Master ${process.pid} is running`); + + for (let i = 0; i < WORKERS_COUNT; i++) { + cluster.fork(); + } + + cluster.on("exit", (worker) => { + console.log(`Worker ${worker.process.pid} died`); + cluster.fork(); + }); + + const httpServer = http.createServer(); + setupMaster(httpServer, { + loadBalancingMethod: "least-connection", // either "random", "round-robin" or "least-connection" + }); + const PORT = process.env.PORT || 3000; + + httpServer.listen(PORT, () => + console.log(`server listening at http://localhost:${PORT}`) + ); +} else { + console.log(`Worker ${process.pid} started`); + require("./index"); +} diff --git a/examples/react-private-messaging/server/docker-compose.yml b/examples/react-private-messaging/server/docker-compose.yml new file mode 100644 index 0000000000..4845950cf6 --- /dev/null +++ b/examples/react-private-messaging/server/docker-compose.yml @@ -0,0 +1,7 @@ +version: "3" + +services: + redis: + image: redis:5 + ports: + - "6379:6379" diff --git a/examples/react-private-messaging/server/index.js b/examples/react-private-messaging/server/index.js new file mode 100644 index 0000000000..1ab99be5c4 --- /dev/null +++ b/examples/react-private-messaging/server/index.js @@ -0,0 +1,125 @@ +const httpServer = require("http").createServer(); +const Redis = require("ioredis"); +const redisClient = new Redis(); +const io = require("socket.io")(httpServer, { + cors: { + origin: "http://localhost:8080", + }, + adapter: require("socket.io-redis")({ + pubClient: redisClient, + subClient: redisClient.duplicate(), + }), +}); + +const { setupWorker } = require("@socket.io/sticky"); +const crypto = require("crypto"); +const randomId = () => crypto.randomBytes(8).toString("hex"); + +const { RedisSessionStore } = require("./sessionStore"); +const sessionStore = new RedisSessionStore(redisClient); + +const { RedisMessageStore } = require("./messageStore"); +const messageStore = new RedisMessageStore(redisClient); + +io.use(async (socket, next) => { + const sessionID = socket.handshake.auth.sessionID; + if (sessionID) { + const session = await sessionStore.findSession(sessionID); + if (session) { + socket.sessionID = sessionID; + socket.userID = session.userID; + socket.username = session.username; + return next(); + } + } + const username = socket.handshake.auth.username; + if (!username) { + return next(new Error("invalid username")); + } + socket.sessionID = randomId(); + socket.userID = randomId(); + socket.username = username; + next(); +}); + +io.on("connection", async (socket) => { + // persist session + sessionStore.saveSession(socket.sessionID, { + userID: socket.userID, + username: socket.username, + connected: true, + }); + + // emit session details + socket.emit("session", { + sessionID: socket.sessionID, + userID: socket.userID, + }); + + // join the "userID" room + socket.join(socket.userID); + + // fetch existing users + const users = []; + const [messages, sessions] = await Promise.all([ + messageStore.findMessagesForUser(socket.userID), + sessionStore.findAllSessions(), + ]); + const messagesPerUser = new Map(); + messages.forEach((message) => { + const { from, to } = message; + const otherUser = socket.userID === from ? to : from; + if (messagesPerUser.has(otherUser)) { + messagesPerUser.get(otherUser).push(message); + } else { + messagesPerUser.set(otherUser, [message]); + } + }); + + sessions.forEach((session) => { + users.push({ + userID: session.userID, + username: session.username, + connected: session.connected, + messages: messagesPerUser.get(session.userID) || [], + }); + }); + socket.emit("users", users); + + // notify existing users + socket.broadcast.emit("user connected", { + userID: socket.userID, + username: socket.username, + connected: true, + messages: [], + }); + + // forward the private message to the right recipient (and to other tabs of the sender) + socket.on("private message", ({ content, to }) => { + const message = { + content, + from: socket.userID, + to, + }; + socket.to(to).to(socket.userID).emit("private message", message); + messageStore.saveMessage(message); + }); + + // notify users upon disconnection + socket.on("disconnect", async () => { + const matchingSockets = await io.in(socket.userID).allSockets(); + const isDisconnected = matchingSockets.size === 0; + if (isDisconnected) { + // notify other users + socket.broadcast.emit("user disconnected", socket.userID); + // update the connection status of the session + sessionStore.saveSession(socket.sessionID, { + userID: socket.userID, + username: socket.username, + connected: false, + }); + } + }); +}); + +setupWorker(io); diff --git a/examples/react-private-messaging/server/messageStore.js b/examples/react-private-messaging/server/messageStore.js new file mode 100644 index 0000000000..60ab0f6f72 --- /dev/null +++ b/examples/react-private-messaging/server/messageStore.js @@ -0,0 +1,54 @@ +/* abstract */ class MessageStore { + saveMessage(message) {} + findMessagesForUser(userID) {} +} + +class InMemoryMessageStore extends MessageStore { + constructor() { + super(); + this.messages = []; + } + + saveMessage(message) { + this.messages.push(message); + } + + findMessagesForUser(userID) { + return this.messages.filter( + ({ from, to }) => from === userID || to === userID + ); + } +} + +const CONVERSATION_TTL = 24 * 60 * 60; + +class RedisMessageStore extends MessageStore { + constructor(redisClient) { + super(); + this.redisClient = redisClient; + } + + saveMessage(message) { + const value = JSON.stringify(message); + this.redisClient + .multi() + .rpush(`messages:${message.from}`, value) + .rpush(`messages:${message.to}`, value) + .expire(`messages:${message.from}`, CONVERSATION_TTL) + .expire(`messages:${message.to}`, CONVERSATION_TTL) + .exec(); + } + + findMessagesForUser(userID) { + return this.redisClient + .lrange(`messages:${userID}`, 0, -1) + .then((results) => { + return results.map((result) => JSON.parse(result)); + }); + } +} + +module.exports = { + InMemoryMessageStore, + RedisMessageStore, +}; diff --git a/examples/react-private-messaging/server/package.json b/examples/react-private-messaging/server/package.json new file mode 100644 index 0000000000..6a0310bcd7 --- /dev/null +++ b/examples/react-private-messaging/server/package.json @@ -0,0 +1,17 @@ +{ + "name": "server", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "node cluster.js" + }, + "author": "Damien Arrachequesne ", + "license": "MIT", + "dependencies": { + "@socket.io/sticky": "^1.0.0", + "ioredis": "^4.22.0", + "socket.io": "^4.0.0", + "socket.io-redis": "^6.0.1" + } +} diff --git a/examples/react-private-messaging/server/sessionStore.js b/examples/react-private-messaging/server/sessionStore.js new file mode 100644 index 0000000000..0ace3f4eb5 --- /dev/null +++ b/examples/react-private-messaging/server/sessionStore.js @@ -0,0 +1,89 @@ +/* abstract */ class SessionStore { + findSession(id) {} + saveSession(id, session) {} + findAllSessions() {} +} + +class InMemorySessionStore extends SessionStore { + constructor() { + super(); + this.sessions = new Map(); + } + + findSession(id) { + return this.sessions.get(id); + } + + saveSession(id, session) { + this.sessions.set(id, session); + } + + findAllSessions() { + return [...this.sessions.values()]; + } +} + +const SESSION_TTL = 24 * 60 * 60; +const mapSession = ([userID, username, connected]) => + userID ? { userID, username, connected: connected === "true" } : undefined; + +class RedisSessionStore extends SessionStore { + constructor(redisClient) { + super(); + this.redisClient = redisClient; + } + + findSession(id) { + return this.redisClient + .hmget(`session:${id}`, "userID", "username", "connected") + .then(mapSession); + } + + saveSession(id, { userID, username, connected }) { + this.redisClient + .multi() + .hset( + `session:${id}`, + "userID", + userID, + "username", + username, + "connected", + connected + ) + .expire(`session:${id}`, SESSION_TTL) + .exec(); + } + + async findAllSessions() { + const keys = new Set(); + let nextIndex = 0; + do { + const [nextIndexAsStr, results] = await this.redisClient.scan( + nextIndex, + "MATCH", + "session:*", + "COUNT", + "100" + ); + nextIndex = parseInt(nextIndexAsStr, 10); + results.forEach((s) => keys.add(s)); + } while (nextIndex !== 0); + const commands = []; + keys.forEach((key) => { + commands.push(["hmget", key, "userID", "username", "connected"]); + }); + return this.redisClient + .multi(commands) + .exec() + .then((results) => { + return results + .map(([err, session]) => (err ? undefined : mapSession(session))) + .filter((v) => !!v); + }); + } +} +module.exports = { + InMemorySessionStore, + RedisSessionStore, +}; diff --git a/examples/react-private-messaging/src/App.css b/examples/react-private-messaging/src/App.css new file mode 100644 index 0000000000..c01137d449 --- /dev/null +++ b/examples/react-private-messaging/src/App.css @@ -0,0 +1,179 @@ +*, +*:before, +*:after { + box-sizing: border-box; +} + +body { + font-family: "Roboto", Arial, sans-serif; + background: #141e30; /* fallback for old browsers */ + background: -webkit-linear-gradient( + to right, + #243b55, + #141e30 + ); /* Chrome 10-25, Safari 5.1-6 */ + background: linear-gradient( + to right, + #243b55, + #141e30 + ); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ +} + +.app-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 80vh; + min-width: 80vw; +} + +/* Chat component */ +.chat-container { + display: grid; + grid-template-areas: "column1 column2"; + grid-template-columns: 30% 70%; + min-width: 80vw; + min-height: 70vh; + border: 1px solid #fff; + border-radius: 10px; + background-color: #f8f3eb; +} + +.user-list { + grid-area: column1; + box-shadow: 0 0 30px rgba(0, 0, 0, 0.1); + margin: 0 5px 0 5px; + padding: 0; + h3 { + margin-bottom: 10px; + } + ul { + list-style: none; + padding: 0; + overflow-y: auto; + } + li { + padding: 5px; + margin-right: 10px; + margin-bottom: 10px; + .name { + margin-right: 5px; + } + .logged-in { + color: green; + } + .logged-out { + color: black; + } + } + li:hover { + border-radius: 6px; + background-color: #aaa; + } + li.user.selected { + border-radius: 6px; + background-color: #aaa; + } +} + +.chat-window { + grid-area: column2; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 20px; + overflow-y: auto; + + .chat-input { + width: 100%; + border-radius: 6px; + border: 1px solid #fff; + padding: 10px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + transition: 0.3s border-color; + } + + .chat-input:hover { + border: 1px solid #e0e0e0; + } + + .chat-messages { + width: 100%; + + .message { + list-style: none; + } + + .message.receive { + text-align: left; + } + + .message.sent { + text-align: right; + } + } + + li.message.received > div.wrapper { + margin-bottom: 20px; + } + + li.message.received > div.wrapper > span { + background-color: #cfcfcf; + color: #000; + border-radius: 6px; + padding: 5px; + } + + li.message.sent > div.wrapper { + margin-bottom: 20px; + } + + li.message.sent > div.wrapper > span { + border-radius: 6px; + background-color: #4355ba; + color: #fae8e8; + padding: 5px; + } +} + +/* Sign in */ +.paper-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.paper { + padding: 60px 50px; + border: 1px solid #fff; + border-radius: 10px; + background-color: #f8f3eb; +} + +.paper-header { + text-align: center; +} + +.formgroup-input { + width: 100%; + margin: 5px 0; + padding: 5px; + border: 1px solid #ebebeb; + border-radius: 6px; + box-shadow: 5px 1px 10px rgba(0, 0, 0, 0.1); + transition: 0.6s border-color; +} +.formgroup-input:hover { + border: 1px solid #aaa; +} + +.formgroup-button { + padding: 5px; + border: none; + background-color: #3f51b5; + border-radius: 6px; + color: #fff; + font-weight: 600; + width: 100%; +} diff --git a/examples/react-private-messaging/src/App.jsx b/examples/react-private-messaging/src/App.jsx new file mode 100644 index 0000000000..fec1e0d68a --- /dev/null +++ b/examples/react-private-messaging/src/App.jsx @@ -0,0 +1,141 @@ +import { useEffect, useState } from "react"; +import SignIn from "./SignIn"; +import Chat from "./Chat"; +import { socket } from "./socket"; +import "./App.css"; + +const defaultUser = { + isLoggedIn: false, + username: null, + userID: null, + connected: false, +}; + +function App() { + const [user, setUser] = useState(defaultUser); + const [allUsers, setAllUsers] = useState([]); + const [selectedUser, setSelectedUser] = useState(); + const [currentChatMsg, setCurrentChatMsg] = useState( + selectedUser?.messages || [] + ); + + const onSignIn = (username) => { + setUser({ + ...user, + isLoggedIn: username.length > 0, + username, + }); + }; + + useEffect(() => { + if (user.isLoggedIn) { + function getAllUsers(users) { + console.log(users); + users = users.sort((a, b) => { + if (a.self) return -1; + if (b.self) return 1; + if (a.username < b.username) return -1; + return a.username > b.username ? 1 : 0; + }); + console.log(users); + setAllUsers(users); + } + function setSession({ sessionID, userID }) { + socket.auth.sessionID = sessionID; + socket.userID = userID; + setUser({ + ...user, + sessionID, + userID, + }); + localStorage.setItem("sessionID", sessionID); + } + function handleConnectError(err) { + if (err.message === "invalid username") { + setUser(defaultUser); + } + } + function addNewUser(user) { + let newList = [...allUsers]; + newList.push(user); + newList = newList.sort((a, b) => { + if (a.self) return -1; + if (b.self) return 1; + if (a.username < b.username) return -1; + return a.username > b.username ? 1 : 0; + }); + setAllUsers(newList); + } + function removeUser(userID) { + let newList = allUsers.filter((au) => au.userID !== userID); + setAllUsers([...newList]); + } + function updateWithMessages({ from, to, content }) { + for (let i = 0; i < allUsers.length; i++) { + const user = allUsers[i]; + if (user.userID === from) { + user.messages.push({ + from, + to, + content, + }); + } + if (user.userID === to) { + user.messages.push({ + from, + to, + content, + }); + } + } + + setAllUsers([...allUsers]); + if (new Set([from, to]).has(selectedUser?.userID)) { + setCurrentChatMsg( + allUsers.filter((u) => u.userID === selectedUser?.userID)[0] + .messages + ); + } + } + if (!socket.auth) { + socket.auth = { username: user.username }; + } + socket.connect(); + socket.on("connect_error", handleConnectError); + socket.on("users", getAllUsers); + socket.on("session", setSession); + socket.on("user connected", addNewUser); + socket.on("user disconnected", removeUser); + socket.on("private message", updateWithMessages); + return () => { + socket.off("connect_error", handleConnectError); + socket.off("users", getAllUsers); + socket.off("session", setSession); + socket.off("user connected", addNewUser); + socket.off("user disconnected", removeUser); + socket.off("private message", updateWithMessages); + }; + } + }, [user.isLoggedIn, allUsers.length]); + + return ( + <> +
+ {user.isLoggedIn ? ( + + ) : ( + + )} +
+ + ); +} + +export default App; diff --git a/examples/react-private-messaging/src/Chat.jsx b/examples/react-private-messaging/src/Chat.jsx new file mode 100644 index 0000000000..56d88b58ab --- /dev/null +++ b/examples/react-private-messaging/src/Chat.jsx @@ -0,0 +1,111 @@ +import "./App.css"; +import { socket } from "./socket"; +import { useState } from "react"; + +export default function Chat({ + currentUser, + users, + selectedUser, + setSelectedUser, + currentChat, + setCurrentChat, +}) { + const [newMsg, setNewMsg] = useState(""); + const handleOnSelectUser = (user) => { + if (user.userID !== currentUser.userID) { + setSelectedUser(user); + setCurrentChat(user.messages); + } + }; + const handleNewMsgKeyDown = (e) => { + if (e.keyCode === 13) { + console.log("socket emit new message"); + const message = { + from: currentUser.userID, + to: selectedUser.userID, + content: newMsg, + }; + socket.emit("private message", message); + selectedUser.messages.push(message); + setSelectedUser(selectedUser); + setNewMsg(""); + } + }; + const renderWhenNoUserOrNoSelectedUser = () => { + let message; + if (users.length === 1) { + message = "Please wait for users to join"; + } else if (selectedUser == null) { + message = "Please select a user to chat with"; + } + return ( +
+

{message}

+
+ ); + }; + + return ( +
+
+

Users

+
    + {users.map((u) => ( +
  • handleOnSelectUser(u)} + > + {u.username} + {u.connected ? ( + + ) : ( + + )} + {u.userID === currentUser.userID &&  (You)} +
  • + ))} +
+
+
+ {users.length === 1 || selectedUser == null ? ( + renderWhenNoUserOrNoSelectedUser() + ) : ( + <> +
    + {currentChat.length > 0 && + currentChat.map((m) => ( +
  • +
    + {m.content} +
    +
  • + ))} +
+ setNewMsg(e.target.value)} + onKeyDown={handleNewMsgKeyDown} + /> + + )} +
+
+ ); +} diff --git a/examples/react-private-messaging/src/SignIn.jsx b/examples/react-private-messaging/src/SignIn.jsx new file mode 100644 index 0000000000..4b5c2a9e70 --- /dev/null +++ b/examples/react-private-messaging/src/SignIn.jsx @@ -0,0 +1,91 @@ +import { + Avatar, + Box, + Button, + Checkbox, + Container, + CssBaseline, + FormControlLabel, + Grid, + Link, + Typography, + TextField, +} from "@mui/material"; +import FaceIcon from "@mui/icons-material/Face"; +import { useState } from "react"; +import "./App.css"; + +{ + /* + + + + + + + Sign in + + + setUsername(e.target.value)} + /> + + + + + */ +} + +export default function SignIn({ onSignIn }) { + const [username, setUsername] = useState(); + const handleSubmit = (e) => { + e.preventDefault(); + if (username && username.length > 3) { + onSignIn(username); + } + }; + + return ( +
+
+

Sign In

+
+ setUsername(e.target.value)} + className="formgroup-input" + id="username" + name="username" + type="text" + placeholder="Username" + required + /> + + +
+
+
+ ); +} diff --git a/examples/react-private-messaging/src/main.jsx b/examples/react-private-messaging/src/main.jsx new file mode 100644 index 0000000000..569fdf2fdd --- /dev/null +++ b/examples/react-private-messaging/src/main.jsx @@ -0,0 +1,9 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App.jsx"; + +ReactDOM.createRoot(document.getElementById("root")).render( + + + +); diff --git a/examples/react-private-messaging/src/socket.js b/examples/react-private-messaging/src/socket.js new file mode 100644 index 0000000000..5740208aeb --- /dev/null +++ b/examples/react-private-messaging/src/socket.js @@ -0,0 +1,10 @@ +import { io } from "socket.io-client"; + +const URL = + process.env.NODE_ENV === "production" ? undefined : "http://localhost:3000"; + +export const socket = io(URL, { + ackTimeout: 5000, + autoConnect: false, + retries: 3, +}); diff --git a/examples/react-private-messaging/vite.config.js b/examples/react-private-messaging/vite.config.js new file mode 100644 index 0000000000..8d70a422da --- /dev/null +++ b/examples/react-private-messaging/vite.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 8080, + }, +}); From b1de41c88d1be948544e8fa2be4e870269a3fe12 Mon Sep 17 00:00:00 2001 From: qi Date: Fri, 22 Dec 2023 22:35:05 -0500 Subject: [PATCH 2/2] Added image for illustration and remove retries until offsets are implemented --- examples/react-private-messaging/README.md | 3 +++ examples/react-private-messaging/image.png | Bin 0 -> 90443 bytes examples/react-private-messaging/src/socket.js | 3 +-- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 examples/react-private-messaging/image.png diff --git a/examples/react-private-messaging/README.md b/examples/react-private-messaging/README.md index ac80695fc4..d8ac0bd0d6 100644 --- a/examples/react-private-messaging/README.md +++ b/examples/react-private-messaging/README.md @@ -18,3 +18,6 @@ npm install npm start ``` +Here's what the interface looks like +![Alt text](image.png) + diff --git a/examples/react-private-messaging/image.png b/examples/react-private-messaging/image.png new file mode 100644 index 0000000000000000000000000000000000000000..124e43934e896b2d63b4da921ff74f6e7c44e5ae GIT binary patch literal 90443 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%0D-^bi9xuqz~# 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