Skip to content
This repository has been archived by the owner on Dec 15, 2023. It is now read-only.

Commit

Permalink
refactoring, doc
Browse files Browse the repository at this point in the history
  • Loading branch information
cedricouellet committed Jan 28, 2022
1 parent 03b452f commit cc334f4
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 45 deletions.
97 changes: 77 additions & 20 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@

const path = require("path");
const helmet = require("helmet");
const http = require("http");
const socketIO = require("socket.io");
const {Socket} = require("socket.io");
const express = require("express");

const { events } = require("./lib/constants");
Expand All @@ -11,69 +13,130 @@ const isZalgo = require("./helpers/isZalgo");

const PORT = parseInt(process.env.PORT) || 3000;

// Create the express listener
const app = express();

// Create an underlying http server using the express listener
// (needed for socket.io)
const server = http.createServer(app);

// Create the socket.io socket for the server

// LINT: SocketIO is a valid function
// noinspection JSValidateTypes
const io = socketIO(server);

// Prevent XSS
app.use(helmet());

// Serve the client website
app.use(express.static(path.join(__dirname, "public")));

// Handle new socket connections
try {
io.on(events.CONNECT, onClientSocketConnected);
} catch (err) {
console.log(`A socket error occured: ${err}`);
}

// Start the server listener
server.listen(PORT, () => {
console.log(`listening on port ${PORT}`);
});

/**
* When a client socket connects
* @param {Socket} socket The client socket that connected.
*/
function onClientSocketConnected(socket) {
console.log(`client socket ${socket.id} connected`);

// When a user joins
socket.on(events.JOIN, (username) => {
joinUser(socket, username);
console.log(`client socket ${socket.id} joined chat as '${username}'`);
});

// When a user disconnects
socket.on(events.DISCONNECT, () => {
disconnectClient(socket);
console.log(`client socket ${socket.id} disconnected`);
});

// When a user sends a message
socket.on(events.MESSAGE, (message) => {
receiveMessage(socket, message);
console.log(`client socket ${socket.id} sent '${message}'`);
});
}

/**
* When a client socket disconnects
* @param {Socket} socket The client socket that disconnected
*/
function disconnectClient(socket) {
try {
const left = users.getById(socket.id);

if (left) {
// We remove the user from the list
users.remove(left.id);

// Then notify all clients that this client has disconnected
io.emit(
events.MESSAGE,
messaging.generateServerMessage(`${left.username} has left the chat.`)
);
}

// Send the updated list of usernames to all clients
io.emit(events.USERS, users.getUsernames());
} catch (err) {
console.log(`An error occured while disconnecting a user: ${err}`);
}
}

function receiveChatMessage(socket, message) {
// noinspection JSCheckFunctionSignatures
/**
* When a user sends a message
* @param {Socket} socket The client socket that sent the message
* @param {string} message The message that was received
*/
function receiveMessage(socket, message) {
try {
// Get the current client that sent the message
const sender = users.getById(socket.id)?.username;

// If their connection was somehow interrupted
if (sender === undefined) {
// Let them know something went wrong
socket.emit(events.MESSAGE,
messaging.generateServerMessage("Your connection was lost...\n\nTry refreshing the page!")
);

return;
}

// Otherwise if their message contains zalgo text
if (isZalgo(message)) {
// Let them know they can't do that
socket.emit(events.MESSAGE,
messaging.generateServerMessage("Nice try, but Zalgo is not allowed...")
);

return;
}

// Create the message that will be sent
const messageObject = {
sender: sender,
text: message.trim(),
};

// Send the message to all clients
io.emit(events.MESSAGE, messageObject);
} catch (err) {
// If something goes wrong with the message, let the sender know
socket.emit(
events.MESSAGE,
messaging.generateServerMessage(
Expand All @@ -86,47 +149,41 @@ function receiveChatMessage(socket, message) {
}
}

/**
* Join a user in the chat
* @param {Socket} socket The socket attached to the client
* @param {string} username The username sent by the client
*/
function joinUser(socket, username) {
try {
// If the username contains zalgo
if (isZalgo(username.trim())) {
// Give them a new username
username = "I think I'm funny because I use Zalgo";
}

// Add the user to the list
const joined = users.add(socket.id, username);

// Send them their verified (and maybe altered) username
socket.emit(events.JOIN, joined.username);

// Welcome the client that joined
socket.emit(
events.MESSAGE,
messaging.generateServerMessage("Welcome to NoChat :-)")
);

// Notify all clients except the one that joined,
// that a new user has joined the chat
socket.broadcast.emit(
events.MESSAGE,
messaging.generateServerMessage(`${joined.username} has joined the chat.`)
);

// Send every user the updates list of usernames
io.emit(events.USERS, users.getUsernames());
} catch (err) {
console.log(`An error occured while joining a user: ${err}`);
}
}

function onClientSocketConnected(socket) {
console.log(`client socket ${socket.id} connected`);

socket.on(events.JOIN, (username) => {
joinUser(socket, username);
console.log(`client socket ${socket.id} joined chat as '${username}'`);
});

socket.on(events.DISCONNECT, () => {
disconnectClient(socket);
console.log(`client socket ${socket.id} disconnected`);
});

socket.on(events.CHAT_MESSAGE, (message) => {
receiveChatMessage(socket, message);
console.log(`client socket ${socket.id} sent '${message}'`);
});
}
8 changes: 7 additions & 1 deletion helpers/isZalgo.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@
* @return {boolean} If the string contains Zalgo or not.
*/
function isZalgo(str) {
// If empty, stop here.
if (str.length === 0) return false;

// Create the zalgo regex (idk how it works exactly, but it works)
const reg = new RegExp(/([aeiouy]\u0308)|[\u0300-\u036f\u0489]/, "gi");

// Julien magic
const charBites = str.match(/[\s\S]{1,3}/g).length;
let rawScore = 0;

// Find the chances of it being zalgo (when a lot of the characters match the regex)
let rawScore = 0;
str.split("").forEach(char => {
if (reg.test(char)) rawScore++;
});

// Determine a boolean value based on the score
return rawScore / charBites >= 1;
}

Expand Down
28 changes: 27 additions & 1 deletion lib/constants.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
/**
* The name of the server bot.
* @type {string}
*/
module.exports.BOT_NAME = "NoChat Bot";

/**
* Socket.io events, including custom entries.
* @type {object}
*/
module.exports.events = {
/**
* Event for client socket connection.
*/
CONNECT: "connection",

/**
* Event for client socket disconnection.
*/
DISCONNECT: "disconnect",

/**
* Event for a connected client socket joining the chat, and sending it its corrected username.
*/
JOIN: "join",

/**
* Event for sending/reading messages to/from the connected client socket.
*/
MESSAGE: "message",
CHAT_MESSAGE: "chat_message",

/**
* Event for sending/replying to the client with the list of usernames.
*/
USERS: "users",
};
5 changes: 4 additions & 1 deletion lib/messaging.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const {BOT_NAME} = require('./constants');
/**
* Generate a message coming from the server.
* @param {string} message The message to send.
* @return {{sender: string, text: string}} The message object containing the sender and text.
* @return {{sender: string, text: string}|any} The message object containing the sender and text.
*/
function generateServerMessage(message) {
return {
Expand All @@ -12,6 +12,9 @@ function generateServerMessage(message) {
};
}

/**
* Server messaging functions
*/
module.exports = {
generateServerMessage,
};
19 changes: 17 additions & 2 deletions lib/users.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
const { BOT_NAME } = require("./constants");

/**
* The list of users
* @type {[{id: string, username: string}]}
*/
let users = [];

/**
* Add a user to the list of users.
* @param {string} id The unique identifier of the user.
* @param {string} username The username of the user.
* @return {{id: string, username: string}} The user that was added.
* @return {{id: string|any, username: string|any}} The user that was added.
*/
function add(id, username) {
// Trim white-spaces
id = id.trim();
username = username.trim();

// Try to find an already existing username
let existing = users.find((user) => user.username === username);

// If a username does exists or it is the same as the bot name
if (existing !== undefined || username === BOT_NAME) {
let i = 1;
// We add the count of duplicates to obtain a unique variation
// While it is still not unique, we keep checking for a unique value
while (existing !== undefined) {
existing = users.find((user) => user.username === `${username}(${i})`);
i++;
}
// Then modify the username to the count
username = `${username}(${i})`;
}

// We assign the user to an object then add it to the list
const user = { id, username };

users.push(user);

// Then return the newly created user.
return user;
}

Expand Down Expand Up @@ -54,6 +66,9 @@ function getUsernames() {
return users.map((user) => user.username);
}

/**
* User operations.
*/
module.exports = {
add,
getById,
Expand Down
Loading

0 comments on commit cc334f4

Please sign in to comment.