1
0
gugdun 10 сар өмнө
parent
commit
225dbab2d8

+ 0 - 26
package-lock.json

@@ -15,7 +15,6 @@
         "dotenv": "^16.5.0",
         "ejs": "^3.1.10",
         "express": "^5.1.0",
-        "express-longpoll": "^0.0.6",
         "express-session": "^1.18.1",
         "passport": "^0.7.0",
         "passport-local": "^1.0.0",
@@ -96,11 +95,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/bluebird": {
-      "version": "3.7.2",
-      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
-      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
-    },
     "node_modules/body-parser": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
@@ -439,11 +433,6 @@
         "node": ">= 0.6"
       }
     },
-    "node_modules/eventemitter2": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-3.0.0.tgz",
-      "integrity": "sha512-Qg+4C6WCHXuw/iE/A9dPJBXCb8FN/VIJEGni1z33diuBWMuNUqHpZ/FUT2/Pd5Tn7ZzFBOYQ2Iv4WcdL4HiDog=="
-    },
     "node_modules/express": {
       "version": "5.1.0",
       "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
@@ -485,16 +474,6 @@
         "url": "https://opencollective.com/express"
       }
     },
-    "node_modules/express-longpoll": {
-      "version": "0.0.6",
-      "resolved": "https://registry.npmjs.org/express-longpoll/-/express-longpoll-0.0.6.tgz",
-      "integrity": "sha512-2weaYjDWNcHhno289OnYL5u/3DAZGoxUnfeSZQWDCrhoCB7nNv7kYitHR7+K7ZhILVYdkY1URH2vmaNFrDjLWw==",
-      "dependencies": {
-        "bluebird": "^3.5.0",
-        "eventemitter2": "3.0.0",
-        "lodash": ">=4.17.5"
-      }
-    },
     "node_modules/express-session": {
       "version": "1.18.1",
       "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz",
@@ -826,11 +805,6 @@
         "node": ">=10"
       }
     },
-    "node_modules/lodash": {
-      "version": "4.17.21",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
-      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
-    },
     "node_modules/math-intrinsics": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",

+ 0 - 1
package.json

@@ -32,7 +32,6 @@
     "dotenv": "^16.5.0",
     "ejs": "^3.1.10",
     "express": "^5.1.0",
-    "express-longpoll": "^0.0.6",
     "express-session": "^1.18.1",
     "passport": "^0.7.0",
     "passport-local": "^1.0.0",

+ 14 - 0
public/css/main.css

@@ -184,6 +184,7 @@ body {
 }
 
 .empty-label {
+    display: none;
     width: 100%;
     margin-top: 16px;
     text-align: center;
@@ -191,6 +192,10 @@ body {
     font-weight: 500;
 }
 
+.empty-label:first-child {
+    display: initial;
+}
+
 .action {
     display: flex;
     justify-content: center;
@@ -217,6 +222,14 @@ body {
     width: 100%;
 }
 
+.message:last-child {
+    margin-top: 8px;
+}
+
+.message:first-child .message-datetime {
+    margin-bottom: 0px;
+}
+
 .message-username {
     margin: 0px 16px 10px 16px;
     font-size: 14pt;
@@ -227,6 +240,7 @@ body {
     margin: 0px 16px 8px 16px;
     font-size: 16pt;
     font-weight: 500;
+    word-break: break-word;
     color: #eee;
 }
 

+ 2 - 7
src/index.js

@@ -14,6 +14,7 @@ const indexRouter = require("./routes/index");
 const authRouter = require("./routes/auth");
 const chatRouter = require("./routes/chat");
 const addRouter = require("./routes/add");
+const longpollRouter = require("./routes/longpoll");
 
 const PORT = process.env.PORT || 5000;
 
@@ -37,13 +38,11 @@ app.use(session({
 }));
 app.use(passport.authenticate("session"));
 
-const longpoll = require("express-longpoll")(app);
-longpoll.create("/poll");
-
 app.use(indexRouter);
 app.use(authRouter);
 app.use(chatRouter);
 app.use(addRouter);
+app.use(longpollRouter);
 
 app.use(function (req, res, next) {
     next(createError(404));
@@ -59,7 +58,3 @@ app.use(function (err, req, res, next) {
 app.listen(PORT, function () {
     console.log(`Listening on port ${PORT}`);
 });
-
-setInterval(function () {
-    longpoll.publish("/poll", { message: "Test" });
-}, 5000);

+ 12 - 3
src/routes/auth.js

@@ -46,7 +46,10 @@ router.post("/login", passport.authenticate("local", {
 router.post("/register", (req, res, next) => {
     var salt = crypto.randomBytes(16);
     crypto.pbkdf2(req.body.password, salt, 310000, 32, "sha256", (err, hashedPassword) => {
-        if (err) { return next(err); }
+        if (err) {
+            console.log(err);
+            return next(err);
+        }
         db.one("INSERT INTO users (username, hashed_password, salt) VALUES ($1, $2, $3) RETURNING id, username", [
             req.body.username,
             hashedPassword,
@@ -56,7 +59,10 @@ router.post("/register", (req, res, next) => {
                 id: data.id,
                 username: data.username
             }, (err) => {
-                if (err) { return next(err); }
+                if (err) {
+                    console.log(err);
+                    return next(err);
+                }
                 res.redirect("/");
             });
         }).catch(err => res.redirect("/register"));
@@ -65,7 +71,10 @@ router.post("/register", (req, res, next) => {
 
 router.get("/logout", function (req, res, next) {
     req.logout(function (err) {
-        if (err) { return next(err); }
+        if (err) {
+            console.log(err);
+            return next(err);
+        }
         res.redirect("/");
     });
 });

+ 6 - 3
src/routes/chat.js

@@ -17,7 +17,9 @@ router.get("/chat/:chat_id", async (req, res) => {
             const messages = await db.any("SELECT username, attachment_id, text, timestamp FROM messages JOIN users ON users.id = user_id WHERE chat_id = $1 ORDER BY messages.timestamp DESC", [ chat?.id ]);
             res.render("layout", {
                 child: await ejs.renderFile(path.join(views, "chat.ejs"), {
+                    username: req.user.username,
                     title: req.params.chat_id,
+                    chatId: chat?.id,
                     empty: messages.length < 1,
                     messages: messages.map((message) => {
                         return {
@@ -48,16 +50,17 @@ router.post("/chat/:chat_id", async (req, res) => {
         try {
             const user = await db.one("SELECT id FROM users WHERE username = $1", [ req.params.chat_id ]);
             const chat = await db.one("SELECT id FROM chats WHERE (user1_id = $1 AND user2_id = $2) OR (user1_id = $2 AND user2_id = $1)", [ req.user.id, user?.id ]);
+            const datetime = new Date().toISOString();
             await db.none("INSERT INTO messages (chat_id, user_id, text, timestamp) VALUES ($1, $2, $3, $4)", [
                 chat?.id,
                 req.user.id,
                 req.body.text,
-                new Date().toISOString()
+                datetime
             ]);
+            res.json({ success: true });
         } catch (err) {
             console.log(err);
-        } finally {
-            res.redirect(`/chat/${req.params.chat_id}`);
+            res.json({ success: false });
         }
     } else {
         res.redirect("/login");

+ 1 - 0
src/routes/index.js

@@ -22,6 +22,7 @@ router.get("/", async (req, res) => {
                 })
             });
         } catch (err) {
+            console.log(err);
             res.render("layout", {
                 child: await ejs.renderFile(path.join(views, "chats.ejs"), {
                     empty: true,

+ 40 - 0
src/routes/longpoll.js

@@ -0,0 +1,40 @@
+// Copyright (c) 2025 gugdun
+// All rights reserved. Unauthorized use, copying, or distribution is strictly prohibited.
+
+const express = require("express");
+const db = require("../db");
+
+const router = express.Router();
+
+router.post("/poll/:id", async (req, res) => {
+    if (req.user) {
+        try {
+            const chat = await db.one("SELECT user1_id, user2_id FROM chats WHERE id = $1", [ req.params.id ]);
+            if (chat?.user1_id !== req.user.id && chat?.user2_id !== req.user.id) {
+                throw "User does not belong to this chat!";
+            }
+            async function checkMessages() {
+                const messages = await db.any("SELECT username, attachment_id, text, timestamp FROM messages JOIN users ON users.id = user_id WHERE chat_id = $1 AND timestamp > $2 ORDER BY messages.timestamp DESC", [ req.params.id, req.body.timestamp ]);
+                if (messages.length > 0) {
+                    res.json({
+                        messages: messages.map((message) => {
+                            return {
+                                username: message.username,
+                                text: message.text,
+                                datetime: message.timestamp
+                            }
+                        })
+                    });
+                } else setTimeout(checkMessages, 500);
+            }
+            checkMessages();
+        } catch (err) {
+            console.log(err);
+            res.json({ success: false });
+        }
+    } else {
+        res.redirect("/login");
+    }
+});
+
+module.exports = router;

+ 59 - 4
src/views/chat.ejs

@@ -5,7 +5,7 @@
         </a>
         <span class="chat-title"><%- title %></span>
     </div>
-    <div class="message-list">
+    <div id="message-list" class="message-list">
         <% if (empty) { %>
             <p class="empty-label">No messages</p>
         <% } %>
@@ -17,15 +17,70 @@
             </div>
         <% }); %>
     </div>
-    <form method="POST" action="/chat/<%- title %>" class="input-form">
-        <input type="text" name="text" placeholder="Enter message..." class="text-input" required />
+    <form onsubmit="return onMessageSubmit(event)" class="input-form">
+        <input type="text" id="message-input" name="text" placeholder="Enter message..." class="text-input" required />
         <input type="submit" value="Send" class="button" />
     </form>
 </div>
 <script>
+    var lastTimestamp = new Date().toISOString()
+    var messageList = document.getElementById("message-list")
+    var messageInput = document.getElementById("message-input")
     var timestamps = document.getElementsByClassName("message-datetime")
     for (var i = 0; i < timestamps.length; i++) {
-        var timestamp = timestamps[i];
+        var timestamp = timestamps[i]
         timestamp.innerText = new Date(timestamp.innerText).toLocaleString()
     }
+    function addMessage(message) {
+        var container = document.createElement("div")
+        var nameSpan = document.createElement("span")
+        var textSpan = document.createElement("span")
+        var datetimeSpan = document.createElement("span")
+        container.className = "message"
+        nameSpan.className = "message-username"
+        textSpan.className = "message-text"
+        datetimeSpan.className = "message-datetime"
+        nameSpan.innerText = message.username
+        textSpan.innerText = message.text
+        datetimeSpan.innerText = new Date(message.datetime).toLocaleString()
+        container.appendChild(nameSpan)
+        container.appendChild(textSpan)
+        container.appendChild(datetimeSpan)
+        messageList.insertBefore(container, messageList.firstChild)
+        lastTimestamp = message.datetime
+    }
+    function onMessageSubmit(event) {
+        event.preventDefault()
+        var message = {
+            username: "<%- username %>",
+            text: messageInput.value,
+            datetime: new Date().toISOString()
+        }
+        var xhr = new XMLHttpRequest()
+        xhr.open("POST", "/chat/<%- title %>")
+        xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8")
+        xhr.send(JSON.stringify(message))
+        messageInput.value = ""
+        return false
+    }
+    function pollMessages() {
+        var data = { timestamp: lastTimestamp }
+        var xhr = new XMLHttpRequest()
+        xhr.timeout = 30000
+        xhr.open("POST", "/poll/<%- chatId %>")
+        xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8")
+        xhr.onload = function () {
+            var response = JSON.parse(xhr.responseText)
+            for (var i = 0; i < response["messages"].length; i++) {
+                var message = response["messages"][i]
+                addMessage(message)
+            }
+            pollMessages()
+        }
+        xhr.ontimeout = function (e) {
+            pollMessages()
+        }
+        xhr.send(JSON.stringify(data))
+    }
+    pollMessages()
 </script>