chookchat/client-web/index.js
2025-03-14 08:44:06 +11:00

702 lines
24 KiB
JavaScript

alert("Chookchat is currently in early development, expect bugs! Please don't try breaking the public server, do that with your own test server (read more in the Git repo). Thanks for trying Chookchat!")
let ws;
let username;
let password;
let typingTimeout;
let typingPeople = new Array();
let api;
let currentRoom = "general";
let availableRooms = ["general"];
function resizeMessaging() {
const messagingDiv = document.getElementById('messaging');
if (messagingDiv) {
messagingDiv.style.width = `${window.innerWidth - 40}px`;
messagingDiv.style.height = `${window.innerHeight - 40}px`;
}
}
resizeMessaging();
window.addEventListener('resize', resizeMessaging);
function showConfig() {
const serverconfig = document.getElementById('serverconfig')
if (serverconfig) {
serverconfig.style.display = 'block';
}
}
function md5(string) {
return CryptoJS.MD5(string).toString();
}
function getUrl() {
const serverUrl = document.getElementById('serverUrl').value.trim();
const serverPort = document.getElementById('serverPort').value;
const useWss = document.getElementById('securityStatus').checked;
const protocol = useWss ? 'wss' : 'ws';
const cleanUrl = serverUrl.replace(/^(https?:\/\/|wss?:\/\/)/, '');
return `${protocol}://${cleanUrl}:${serverPort}/api/websocket`;
}
function getSignupUrl() {
const serverUrl = document.getElementById('serverUrl').value.trim();
const serverPort = document.getElementById('serverPort').value;
const useWss = document.getElementById('securityStatus').checked;
const protocol = useWss ? 'https' : 'http';
const cleanUrl = serverUrl.replace(/^(https?:\/\/|wss?:\/\/)/, '');
return `${protocol}://${cleanUrl}:${serverPort}/api/createaccount/`;
}
function getUploadUrl() {
const serverUrl = document.getElementById('serverUrl').value.trim();
const serverPort = document.getElementById('serverPort').value;
const useWss = document.getElementById('securityStatus').checked;
const protocol = useWss ? 'https' : 'http';
const cleanUrl = serverUrl.replace(/^(https?:\/\/|wss?:\/\/)/, '');
return `${protocol}://${cleanUrl}:${serverPort}/api/upload`;
}
async function getRooms() {
try {
const serverUrl = document.getElementById('serverUrl').value.trim();
const serverPort = document.getElementById('serverPort').value;
const useWss = document.getElementById('securityStatus').checked;
const protocol = useWss ? 'https' : 'http';
const cleanUrl = serverUrl.replace(/^(https?:\/\/|wss?:\/\/)/, '');
const url = `${protocol}://${cleanUrl}:${serverPort}/api/rooms`;
const response = await fetch(url, {
mode: "no-cors"
});
const data = await response.json();
return JSON.parse(data.content);
} catch (error) {
console.error('Error fetching rooms:', error);
return ["general"];
}
}
async function getRoomHistory(roomName) {
try {
const serverUrl = document.getElementById('serverUrl').value.trim();
const serverPort = document.getElementById('serverPort').value;
const useWss = document.getElementById('securityStatus').checked;
const protocol = useWss ? 'https' : 'http';
const cleanUrl = serverUrl.replace(/^(https?:\/\/|wss?:\/\/)/, '');
const url = `${protocol}://${cleanUrl}:${serverPort}/api/room/${roomName}/history`;
const response = await fetch(url, {
mode: "no-cors"
});
const history = await response.text();
return history;
} catch (error) {
console.error('Error fetching room history:', error);
return '';
}
}
const imageMimeTypes = [
'image/webp',
'image/tiff',
'image/svg+xml',
'image/png',
'image/jpeg',
'image/vnd.microsoft.icon',
'image/gif',
'image/bmp',
];
const imageTypes = [
'png',
'jpg',
'jpeg',
'svg',
'tiff',
'gif',
'webp',
'bmp'
];
function isImage(file) {
const fileSplit = file.split(".");
return imageTypes.includes(fileSplit[fileSplit.length - 1]);
}
async function connect() {
username = document.getElementById('username').value;
password = document.getElementById('password').value;
if (!username || !password) {
alert('Please enter a username and password');
return;
}
const wsUrl = getUrl();
if (ws) {
ws.close();
}
ws = new WebSocket(wsUrl);
var incorrectDetail = 0;
ws.onopen = async () => {
if (typeof Notification !== "undefined") {
Notification.requestPermission();
}
console.log('Connected!');
document.getElementById('login').style.display = 'none';
document.getElementById('messaging').style.display = 'block';
try {
availableRooms = await getRooms();
updateRoomList();
} catch (error) {
console.error('Failed to get room list:', error);
availableRooms = ["general"];
}
const connectMessage = {
"type": "connect",
"username": username,
"token": md5(password),
"content": `${username} joined the room!`
}
ws.send(JSON.stringify(connectMessage));
joinRoom("general");
ws.onmessage = (event) => {
if (event.data === "ping") {
ws.send("pong");
return;
}
const message = JSON.parse(event.data);
if (message.type == "error") {
if (message.username == "system") {
if (message.content == "invalid-token") {
alert("Your password is incorrect! Please try putting in your password right.");
incorrectDetail = 1;
location.reload();
}
if (message.content == "unknown-account") {
alert("That username isn't on the server. Maybe try registering?");
incorrectDetail = 1;
location.reload();
}
if (message.content == "banned") {
alert("kiddo you're banned lol what did you do to get banned lmaooo");
incorrectDetail = 1;
location.reload();
}
}
}
else if (message.type == "typing" && message.content == "1") {
if (username !== message.username && !typingPeople.includes(message.username)) {
typingPeople.push(message.username);
updatePeopleTyping();
}
return;
}
else if (message.type == "typing" && message.content == "0") {
if (username !== message.username && typingPeople.includes(message.username)) {
const index = typingPeople.indexOf(message.username);
typingPeople.splice(index, 1);
updatePeopleTyping();
}
return;
}
else if (message.type == "users" && message.username == "system") {
usersDiv = document.getElementById("users");
if (usersDiv) {
usersDiv.textContent = `Online users: ${message.content}`
}
return;
}
else if (message.type == "roomUsers" && message.username == "system") {
const usersInRoom = message.content;
const roomName = message.room;
usersDiv = document.getElementById("users");
if (usersDiv) {
usersDiv.textContent = `Users in ${roomName}: ${usersInRoom}`;
if (roomName !== currentRoom) {
currentRoom = roomName;
updateCurrentRoomDisplay();
}
}
return;
}
else if (message.type == "roomsList" && message.username == "system") {
try {
availableRooms = JSON.parse(message.content);
updateRoomList();
} catch (error) {
console.error('Error parsing rooms list:', error);
}
return;
}
else if (message.type == "roomCreated" || message.type == "roomJoin") {
if (message.room) {
currentRoom = message.room;
updateCurrentRoomDisplay();
clearMessages();
loadRoomHistory(currentRoom);
updateRoomList();
}
// Display the system message
const messagesDiv = document.getElementById('messagebox');
const messageElement = document.createElement('div');
if (messageElement && messagesDiv) {
messagesDiv.appendChild(messageElement);
messageElement.className = 'message system-message';
messageElement.textContent = message.content;
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
return;
}
else if (message.type == "file") {
// Only show file if it's for current room
if (message.room && message.room !== currentRoom) {
return;
}
const messagesDiv = document.getElementById('messagebox');
let filename = message.content.replace("https://maxwellj.xyz/chookchat/uploads/", "");
if (isImage(filename)) {
const imagePreview = document.createElement('img');
if (imagePreview) {
if (messagesDiv) {
messagesDiv.appendChild(imagePreview);
imagePreview.src = message.content;
imagePreview.height = 300;
imagePreview.addEventListener("click", function() {
window.open(message.content, "_blank");
});
}
}
} else {
const fileButton = document.createElement('button');
if (fileButton) {
if (messagesDiv) {
messagesDiv.appendChild(fileButton);
fileButton.textContent = `Open ${filename} in new tab`;
fileButton.className = "bluebutton";
fileButton.addEventListener("click", function() {
window.open(message.content, "_blank");
});
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
}
}
return;
}
else if (message.type == "call") {
if (message.room && message.room !== currentRoom) {
return;
}
const messagesDiv = document.getElementById('messagebox');
const callButton = document.createElement('div');
if (callButton) {
if (messagesDiv) {
messagesDiv.appendChild(callButton)
callButton.className = "message call-notification";
callButton.textContent = `${message.username} started a Jitsi call! Click to join!`;
callButton.addEventListener('click', () => {
window.open(message.content, '_blank');
});
messagesDiv.scrollTop = messagesDiv.scrollHeight;
return;
}
}
}
else if (message.type == "connect") {
const messagesDiv = document.getElementById('messagebox');
const messageElement = document.createElement('div');
if (messageElement) {
if (messagesDiv) {
messagesDiv.appendChild(messageElement);
messageElement.className = 'message';
messageElement.textContent = message.content;
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
}
if (document.hidden) {
const notifiction = new Notification("Chookchat", {body: messageElement.textContent});
}
}
else if (message.type == "message") {
if (message.room && message.room !== currentRoom) {
return;
}
const messagesDiv = document.getElementById('messagebox');
const messageElement = document.createElement('div');
if (messageElement) {
if (messagesDiv) {
messagesDiv.appendChild(messageElement);
messageElement.className = 'message';
messageElement.textContent = `${message.username}: ${message.content}` ;
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
}
if (document.hidden) {
const notifiction = new Notification("Chookchat", {body: messageElement.textContent});
}
}
else {
return
}
};
}
ws.onclose = () => {
alert("Chookchat has disconnected :/ Refresh the page to try again");
}
}
async function createRoom(roomName) {
const message = {
type: 'createRoom',
username: username,
token: md5(password),
room: roomName,
content: ""
};
ws.send(JSON.stringify(message));
setTimeout(updateRoomList, 500);
}
async function joinRoom(roomName) {
if (roomName === currentRoom) {
const usersMessage = {
type: 'getUsersInRoom',
username: username,
token: md5(password),
room: roomName,
content: ""
};
ws.send(JSON.stringify(usersMessage));
return;
}
const message = {
type: 'joinRoom',
username: username,
token: md5(password),
room: roomName,
content: ""
};
ws.send(JSON.stringify(message));
}
function sendMessage() {
const messageInput = document.getElementById('messageInput');
const message = messageInput.value.trim();
const processedMessage = {
"type": "message",
"username": username,
"token": md5(password),
"room": currentRoom,
"content": message
}
if (processedMessage.content && ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(processedMessage));
messageInput.value = '';
if (typingTimeout) {
clearTimeout(typingTimeout);
}
const stoppedTypingMessage = {
"type": "typing",
"username": username,
"token": md5(password),
"room": currentRoom,
"content": "0"
};
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(stoppedTypingMessage));
}
}
}
function showFileUpload() {
const fileUploadElement = document.getElementById("upload");
if (fileUploadElement) {
fileUploadElement.style.display = "block";
}
}
async function uploadFile() {
const fileInput = document.getElementById("fileupload");
if (!fileInput.files.length) {
alert("Please add a file!");
return;
}
const formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("room", currentRoom);
try {
const response = await fetch(getUploadUrl(), {
method: 'POST',
mode: "no-cors",
body: formData
});
if (response.ok) {
const result = await response.text();
const processedMessage = {
"type": "message",
"username": username,
"token": md5(password),
"room": currentRoom,
"content": `Sent a file`
}
if (processedMessage && ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(processedMessage));
}
} else {
alert("Something went wrong lmao");
}
} catch (error) {
alert(error);
}
const fileUploadElement = document.getElementById("upload");
if (fileUploadElement) {
fileUploadElement.style.display = "none";
}
}
document.getElementById('messageInput').addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
sendMessage();
}
});
document.getElementById('password').addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
connect();
}
});
async function doRegister(username, password) {
return fetch(`${getSignupUrl()}username:{${username}}token:{${md5(password)}}`).then((response)=>response.json()).then((responseJson)=>{return responseJson});
}
async function register() {
username = document.getElementById('username').value;
password = document.getElementById('password').value;
if (!username || !password) {
alert('Please enter a username and password');
return;
}
const response = await this.doRegister(username, password);
if (response.type == "success") {
alert("Account created! Click 'log in' to access Chookchat!")
} else {
alert(`We couldn't create your account :( Reason: ${response.content}`)
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function startTypingIndicator() {
if (typingTimeout) {
clearTimeout(typingTimeout);
}
const typingMessage = {
"type": "typing",
"username": username,
"token": md5(password),
"room": currentRoom,
"content": "1"
};
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(typingMessage));
}
typingTimeout = setTimeout(() => {
const stoppedTypingMessage = {
"type": "typing",
"username": username,
"token": md5(password),
"room": currentRoom,
"content": "0"
};
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(stoppedTypingMessage));
}
}, 5000);
}
function updatePeopleTyping() {
const typingDiv = document.getElementById('typing');
if (typingDiv) {
if (typingPeople.length === 0) {
typingDiv.textContent = '';
} else if (typingPeople.length === 1) {
typingDiv.textContent = `${typingPeople[0]} is typing...`;
} else if (typingPeople.length === 2) {
typingDiv.textContent = `${typingPeople[0]} and ${typingPeople[1]} are typing...`;
} else {
typingDiv.textContent = `${typingPeople.length} people are typing...`;
}
}
}
const characters ='abcdefghijklmnopqrstuvwxyz';
function generateString(length) {
let result = '';
const charactersLength = characters.length;
for ( let i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
function startMeeting() {
const link = `https://meet.jit.si/chookchat-${generateString(15)}`;
alert("Note: You may need to sign in to Jitsi to start the meeting. We'll take you there in a moment...")
window.open(link, '_blank');
const processedMessage = {
"type": "call",
"username": username,
"token": md5(password),
"room": currentRoom,
"content": link
};
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(processedMessage));
} else {
alert("Something went wrong. Refreshing might do the trick :)")
}
}
document.getElementById('messageInput').addEventListener('input', startTypingIndicator);
async function updateRoomList() {
let roomListDiv = document.getElementById('room-list');
if (!roomListDiv) {
const messagingDiv = document.querySelector('.messaging-container');
roomListDiv = document.createElement('div');
roomListDiv.id = 'room-list';
roomListDiv.className = 'room-list';
messagingDiv.insertBefore(roomListDiv, messagingDiv.firstChild);
}
try {
availableRooms = await getRooms();
} catch (error) {
console.error('Error updating room list:', error);
}
roomListDiv.innerHTML = '<div class="room-header">Rooms</div>';
availableRooms.forEach(room => {
const roomElement = document.createElement('div');
roomElement.className = `room-item ${room === currentRoom ? 'active' : ''}`;
roomElement.textContent = room;
roomElement.addEventListener('click', () => joinRoom(room));
roomListDiv.appendChild(roomElement);
});
const createRoomButton = document.createElement('button');
createRoomButton.className = 'create-room-button';
createRoomButton.textContent = '+ New Room';
createRoomButton.addEventListener('click', promptCreateRoom);
roomListDiv.appendChild(createRoomButton);
}
function updateCurrentRoomDisplay() {
const roomTitle = document.getElementById('room-title');
if (roomTitle) {
roomTitle.remove();
}
const roomItems = document.querySelectorAll('.room-item');
roomItems.forEach(item => {
if (item.textContent === currentRoom) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
}
function clearMessages() {
const messagebox = document.getElementById('messagebox');
if (messagebox) {
messagebox.innerHTML = '';
}
}
async function loadRoomHistory(roomName) {
try {
const history = await getRoomHistory(roomName);
if (history) {
const messagebox = document.getElementById('messagebox');
const lines = history.split('\n');
lines.forEach(line => {
if (line.trim()) {
const messageElement = document.createElement('div');
messageElement.className = 'message history-message';
messageElement.textContent = line;
messagebox.appendChild(messageElement);
}
});
messagebox.scrollTop = messagebox.scrollHeight;
}
} catch (error) {
console.error('Error loading room history:', error);
}
}
function promptCreateRoom() {
const roomName = prompt('Enter a name for the new room:');
if (roomName && roomName.trim()) {
createRoom(roomName.trim());
}
}
function showEggs() {
const eggsPanel = document.getElementById('eggs');
eggsPanel.style.display = "block";
const mainBox = document.querySelector('#messaging .box');
if (eggsPanel.classList.contains('visible')) {
mainBox.style.width = 'calc(100% - 310px)';
} else {
mainBox.style.width = '100%';
}
}
function closeEggs() {
const eggsPanel = document.getElementById('eggs');
eggsPanel.style.display = "block";
}
const uploadField = document.getElementById("fileupload");
uploadField.onchange = function() {
if(this.files[0].size > 10485760) {
alert("That file is too big bro. Not as big as my (message terminated)");
this.value = "";
}
};
// Eggs begin here