start working on friends system
This commit is contained in:
42
client/friends.html
Normal file
42
client/friends.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>friends</title>
|
||||
<link rel="stylesheet" type="text/css" href="/style.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="/script.js"></script>
|
||||
<script src="/script_friends.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<div id="brand">
|
||||
<h2><a href="/">chookchat</a></h2>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li><a id="loginLink" href="/login">login</a></li>
|
||||
<li><a href="/friends">friends</a></li>
|
||||
<li><a href="/settings">settings</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div id="friends">
|
||||
<div id="friends-list">
|
||||
<div id="friends-header">
|
||||
<h1>friends</h1>
|
||||
</div>
|
||||
<div id="friends-list-links">
|
||||
<!-- List of friends inserted here -->
|
||||
</div>
|
||||
</div>
|
||||
<div id="friends-messenger">
|
||||
<div id="friends-messages">
|
||||
<!-- List of messages inserted here -->
|
||||
</div>
|
||||
<div id="friends-message-box">
|
||||
<textarea id="friends-textarea"></textarea>
|
||||
<button onclick="sendMessage()">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
<ul>
|
||||
<li><a id="loginLink" href="/login">login</a></li>
|
||||
<li><a href="/friends">friends</a></li>
|
||||
<li><a href="/settings">settings</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<ul>
|
||||
<li><a id="loginLink" href="/login">login</a></li>
|
||||
<li><a href="/friends">friends</a></li>
|
||||
<li><a href="/settings">settings</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
BIN
client/notify.opus
Normal file
BIN
client/notify.opus
Normal file
Binary file not shown.
@@ -104,7 +104,7 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
// update nav link / redirect regardless of whether a postbox exists
|
||||
const loginLink = document.getElementById("loginLink");
|
||||
if (userStatus.username === "") {
|
||||
if (window.location.pathname === "/settings") {
|
||||
if (window.location.pathname === "/settings" || window.location.pathname === "/friends") {
|
||||
window.location.href = "/login";
|
||||
return;
|
||||
}
|
||||
|
||||
240
client/script_friends.js
Normal file
240
client/script_friends.js
Normal file
@@ -0,0 +1,240 @@
|
||||
class Message {
|
||||
sender = 0;
|
||||
content = "";
|
||||
|
||||
constructor(sender, content) {
|
||||
this.sender = sender;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
// websocket connection
|
||||
/** @type {WebSocket} */
|
||||
let socket;
|
||||
|
||||
// array of userId's
|
||||
/** @type {Array<number>} */
|
||||
let connectedFriends = [];
|
||||
|
||||
// map of userId's to arrays of Message objects
|
||||
/** @type {Map<number, Array<Message>>} */
|
||||
let recievedMessages = new Map();
|
||||
|
||||
// map of userId's to usernames
|
||||
/** @type {Map<number, string>} */
|
||||
let usernames = new Map();
|
||||
|
||||
// userId of the current user who is being talked to
|
||||
let currentUser = 0;
|
||||
|
||||
// userId of the logged-in user (this client). Fetched from /me on load.
|
||||
// /me responds with headers X-Logged-In, X-Username, X-Bio, X-User-ID
|
||||
// (no body), so we read the id back out of the response headers.
|
||||
let selfId = 0;
|
||||
|
||||
// notification sound, loaded after ws initialization
|
||||
let sound;
|
||||
|
||||
async function fetchSelfId() {
|
||||
try {
|
||||
const res = await fetch("/me");
|
||||
if (!res.ok) {
|
||||
console.error("Failed to fetch /me:", res.status);
|
||||
return;
|
||||
}
|
||||
const loggedIn = res.headers.get("X-Logged-In");
|
||||
if (loggedIn !== "true") {
|
||||
console.log("Not logged in");
|
||||
return;
|
||||
}
|
||||
const idHeader = res.headers.get("X-User-ID");
|
||||
selfId = parseInt(idHeader, 10);
|
||||
} catch (err) {
|
||||
console.error("Error fetching /me:", err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} id
|
||||
* @returns {string}
|
||||
*/
|
||||
async function fetchUsername(id) {
|
||||
try {
|
||||
const res = await fetch("/userinfo/" + id);
|
||||
if (!res.ok) {
|
||||
console.error("Failed to fetch /userinfo/" + id + " :", res.status);
|
||||
return;
|
||||
}
|
||||
const usernameHeader = res.headers.get("X-Username");
|
||||
usernames.set(id, usernameHeader);
|
||||
return usernameHeader;
|
||||
} catch (err) {
|
||||
console.error("Error fetching /me:", err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
fetchSelfId();
|
||||
|
||||
if (typeof Notification !== "undefined") {
|
||||
Notification.requestPermission();
|
||||
}
|
||||
|
||||
// create a websocket
|
||||
socket = new WebSocket("ws://" + document.location.hostname + ":" + document.location.port + "/friends/ws");
|
||||
|
||||
socket.onopen = function() {
|
||||
console.log("connected to server!");
|
||||
setInterval(() => { // ping the server every 5 seconds
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(JSON.stringify({ type: 3, userId: 0 }));
|
||||
}
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
socket.onmessage = function(event) {
|
||||
console.log("recieved data", event.data);
|
||||
const data = JSON.parse(event.data);
|
||||
switch (data.type) {
|
||||
case "ok": {
|
||||
console.log("Server is ok");
|
||||
break;
|
||||
}
|
||||
case "error": {
|
||||
console.error("Server sent an error:", data.content);
|
||||
break;
|
||||
}
|
||||
case "message": {
|
||||
console.log("message from", data.userId, ":", data.content);
|
||||
// update the messages variable
|
||||
if (recievedMessages.get(data.userId) === undefined) {
|
||||
recievedMessages.set(data.userId, []);
|
||||
}
|
||||
recievedMessages.get(data.userId).push(new Message(data.userId, data.content));
|
||||
if (currentUser === data.userId) {
|
||||
updateMessages();
|
||||
}
|
||||
// send a notification to the user
|
||||
if (document.hidden || currentUser != data.userId) {
|
||||
if (Notification !== undefined) {
|
||||
new Notification(usernames.get(data.userId), {body: data.content});
|
||||
}
|
||||
if (audio !== undefined) {
|
||||
audio.play();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "users": {
|
||||
console.log("updating user list");
|
||||
connectedFriends = data.content;
|
||||
updateOnlineUsers();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
socket.onerror = function(error) {
|
||||
console.log(error);
|
||||
alert("There was an error with the socket! Check console logs for details. Refresh the page to reconnect");
|
||||
socket.close();
|
||||
}
|
||||
|
||||
socket.onclose = function(event) {
|
||||
console.log("disconnected from server");
|
||||
}
|
||||
|
||||
audio = new Audio();
|
||||
audio.preload = "auto";
|
||||
audio.src = "/notify.opus";
|
||||
document.body.appendChild(audio);
|
||||
|
||||
});
|
||||
|
||||
function updateOnlineUsers() {
|
||||
const friendsList = document.getElementById("friends-list-links");
|
||||
friendsList.innerHTML = "";
|
||||
|
||||
// Get everyone's username
|
||||
for (const user of connectedFriends) {
|
||||
if (usernames.get(user) === undefined) {
|
||||
fetchUsername(user);
|
||||
}
|
||||
}
|
||||
|
||||
for (const user of connectedFriends) {
|
||||
const newElement = document.createElement("p");
|
||||
newElement.textContent = usernames.get(user);
|
||||
newElement.onclick = function() {
|
||||
openUser(user);
|
||||
}
|
||||
friendsList.appendChild(newElement);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {Message} message */
|
||||
function renderMessage(message) {
|
||||
const messagesList = document.getElementById("friends-messages");
|
||||
const newElement = document.createElement("p");
|
||||
const who = message.sender === selfId ? "You" : usernames.get(message.sender);
|
||||
newElement.textContent = who + ": " + message.content;
|
||||
messagesList.appendChild(newElement);
|
||||
messagesList.scrollTop = messagesList.scrollHeight;
|
||||
}
|
||||
|
||||
function updateMessages() {
|
||||
const messages = recievedMessages.get(currentUser);
|
||||
if (messages === undefined || messages.length === 0) {
|
||||
return;
|
||||
}
|
||||
const message = messages[messages.length - 1];
|
||||
renderMessage(message);
|
||||
}
|
||||
|
||||
/** @param {number} user */
|
||||
function openUser(user) {
|
||||
currentUser = user;
|
||||
|
||||
const messagesList = document.getElementById("friends-messages");
|
||||
messagesList.innerHTML = "";
|
||||
|
||||
const messages = recievedMessages.get(user);
|
||||
if (messages === undefined) {
|
||||
return;
|
||||
}
|
||||
for (const message of messages) {
|
||||
renderMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
if (currentUser === 0) {
|
||||
alert("Select a friend to message first");
|
||||
return;
|
||||
}
|
||||
|
||||
const textarea = document.getElementById("friends-textarea");
|
||||
const content = textarea.value.trim();
|
||||
if (content === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (socket.readyState !== WebSocket.OPEN) {
|
||||
alert("Not connected to the server. Refresh the page and try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
socket.send(JSON.stringify({ type: 0, userId: currentUser, content: content }));
|
||||
|
||||
// optimistically show the message locally, since the server only
|
||||
// relays messages between the two other parties, not back to the sender
|
||||
if (recievedMessages.get(currentUser) === undefined) {
|
||||
recievedMessages.set(currentUser, []);
|
||||
}
|
||||
const sentMessage = new Message(selfId, content);
|
||||
recievedMessages.get(currentUser).push(sentMessage);
|
||||
renderMessage(sentMessage);
|
||||
|
||||
textarea.value = "";
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>posts</title>
|
||||
<title>settings</title>
|
||||
<link rel="stylesheet" type="text/css" href="/style.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="/script.js"></script>
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<ul>
|
||||
<li><a id="loginLink" href="/login">login</a></li>
|
||||
<li><a href="/friends">friends</a></li>
|
||||
<li><a href="/settings">settings</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -103,3 +103,32 @@ nav {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Friends-specific CSS */
|
||||
#friends {
|
||||
display: flex;
|
||||
padding-left: 16px;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#friends-list {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#friends-messenger {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
#friends-message-box {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user