From 3a0af4ace27d217146a0a1ab3a7a51560dba88c2 Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Mon, 10 Jun 2024 16:32:52 -0400 Subject: [PATCH 01/16] Adding syntax highlights --- app.js | 19 +++++++++++++++++-- index.html | 2 ++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 6c3dbf7..de456ba 100644 --- a/app.js +++ b/app.js @@ -162,6 +162,11 @@ async function initialize() { swarm.on('close', () => { console.log('Swarm closed'); }); + + // Initialize highlight.js once the DOM is fully loaded + document.addEventListener("DOMContentLoaded", (event) => { + hljs.highlightAll(); + }); } function registerUser(e) { @@ -408,7 +413,17 @@ function onMessageAdded(from, message, avatar) { const $text = document.createElement('div'); $text.classList.add('message-text'); - const md = window.markdownit(); + const md = window.markdownit({ + highlight: function (str, lang) { + if (lang && hljs.getLanguage(lang)) { + try { + return hljs.highlight(str, { language: lang }).value; + } catch (__) {} + } + return ''; // use external default escaping + } + }); + const markdownContent = md.render(message); $text.innerHTML = markdownContent; @@ -462,4 +477,4 @@ function updatePortInUrl(url) { return urlObject.toString(); } -initialize(); \ No newline at end of file +initialize(); diff --git a/index.html b/index.html index ffe7718..f1b1e9e 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,8 @@ + +
From 25e421e98234179ff99dbc52a0a2bfd0027f1a2f Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Mon, 10 Jun 2024 17:13:20 -0400 Subject: [PATCH 02/16] more work on file attachments --- app.js | 74 ++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/app.js b/app.js index de456ba..3eb829f 100644 --- a/app.js +++ b/app.js @@ -79,20 +79,7 @@ async function initialize() { attachFileButton.addEventListener('click', () => fileInput.click()); } if (fileInput) { - fileInput.addEventListener('change', async (event) => { - const file = event.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = async (event) => { - const buffer = new Uint8Array(event.target.result); - const filePath = `/files/${file.name}`; - await drive.put(filePath, buffer); - const fileUrl = `http://localhost:${servePort}${filePath}`; - sendFileMessage(config.userName, fileUrl, file.type, config.userAvatar); - }; - reader.readAsArrayBuffer(file); - } - }); + fileInput.addEventListener('change', handleFileInput); } const configExists = fs.existsSync("./config.json"); @@ -115,15 +102,30 @@ async function initialize() { } eventEmitter.on('onMessage', async (messageObj) => { + console.log('Received message:', messageObj); // Debugging log + if (messageObj.type === 'icon') { const username = messageObj.username; - const avatarBuffer = Buffer.from(messageObj.avatar, 'base64'); - await drive.put(`/icons/${username}.png`, avatarBuffer); - updateIcon(username, avatarBuffer); + if (messageObj.avatar) { + const avatarBuffer = Buffer.from(messageObj.avatar, 'base64'); + await drive.put(`/icons/${username}.png`, avatarBuffer); + updateIcon(username, avatarBuffer); + } else { + console.error('Received icon message with missing avatar data:', messageObj); + } } else if (messageObj.type === 'file') { - addFileMessage(messageObj.name, messageObj.fileName, messageObj.fileUrl, messageObj.fileType, messageObj.avatar); - } else { + if (messageObj.file && messageObj.fileName) { + const fileBuffer = Buffer.from(messageObj.file, 'base64'); + await drive.put(`/files/${messageObj.fileName}`, fileBuffer); + const fileUrl = `http://localhost:${servePort}/files/${messageObj.fileName}`; + addFileMessage(messageObj.name, messageObj.fileName, updatePortInUrl(fileUrl), messageObj.fileType, updatePortInUrl(messageObj.avatar)); + } else { + console.error('Received file message with missing file data or fileName:', messageObj); + } + } else if (messageObj.type === 'message') { onMessageAdded(messageObj.name, messageObj.message, messageObj.avatar); + } else { + console.error('Received unknown message type:', messageObj); } }); @@ -323,6 +325,38 @@ function sendMessage(e) { } } +async function handleFileInput(event) { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = async (event) => { + const buffer = new Uint8Array(event.target.result); + const filePath = `/files/${file.name}`; + await drive.put(filePath, buffer); + const fileUrl = `http://localhost:${servePort}${filePath}`; + + const fileMessage = { + type: 'file', + name: config.userName, + fileName: file.name, + file: buffer.toString('base64'), + fileType: file.type, + avatar: updatePortInUrl(config.userAvatar), + }; + + console.log('Sending file message:', fileMessage); // Debugging log + + const peers = [...swarm.connections]; + for (const peer of peers) { + peer.write(JSON.stringify(fileMessage)); + } + + addFileMessage(config.userName, file.name, fileUrl, file.type, config.userAvatar); + }; + reader.readAsArrayBuffer(file); + } +} + function sendFileMessage(name, fileUrl, fileType, avatar) { const fileName = fileUrl.split('/').pop(); const messageObj = JSON.stringify({ @@ -444,7 +478,7 @@ async function updateIcon(username, avatarBuffer) { if (userIcon) { const avatarBlob = new Blob([avatarBuffer], { type: 'image/png' }); const avatarUrl = URL.createObjectURL(avatarBlob); - userIcon.src = avatarUrl; + userIcon.src = updatePortInUrl(avatarUrl); config.userAvatar = avatarUrl; writeConfigToFile("./config.json"); From e1153cb5dfb313086fa01b98e1e01825cbd44f8e Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Mon, 10 Jun 2024 17:18:06 -0400 Subject: [PATCH 03/16] Fixed Message attachments! --- app.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app.js b/app.js index 3eb829f..ff0e68c 100644 --- a/app.js +++ b/app.js @@ -107,7 +107,7 @@ async function initialize() { if (messageObj.type === 'icon') { const username = messageObj.username; if (messageObj.avatar) { - const avatarBuffer = Buffer.from(messageObj.avatar, 'base64'); + const avatarBuffer = b4a.from(messageObj.avatar, 'base64'); await drive.put(`/icons/${username}.png`, avatarBuffer); updateIcon(username, avatarBuffer); } else { @@ -115,7 +115,7 @@ async function initialize() { } } else if (messageObj.type === 'file') { if (messageObj.file && messageObj.fileName) { - const fileBuffer = Buffer.from(messageObj.file, 'base64'); + const fileBuffer = b4a.from(messageObj.file, 'base64'); await drive.put(`/files/${messageObj.fileName}`, fileBuffer); const fileUrl = `http://localhost:${servePort}/files/${messageObj.fileName}`; addFileMessage(messageObj.name, messageObj.fileName, updatePortInUrl(fileUrl), messageObj.fileType, updatePortInUrl(messageObj.avatar)); @@ -140,7 +140,7 @@ async function initialize() { const iconMessage = JSON.stringify({ type: 'icon', username: config.userName, - avatar: iconBuffer.toString('base64'), + avatar: b4a.toString(iconBuffer, 'base64'), }); connection.write(iconMessage); } @@ -339,7 +339,7 @@ async function handleFileInput(event) { type: 'file', name: config.userName, fileName: file.name, - file: buffer.toString('base64'), + file: b4a.toString(buffer, 'base64'), fileType: file.type, avatar: updatePortInUrl(config.userAvatar), }; From 01fee56692ecef50e421f7243ca166870e0a7e68 Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Mon, 10 Jun 2024 19:49:46 -0400 Subject: [PATCH 04/16] Fixing bot messages --- app.js | 1 - chatBot/includes/Client.js | 35 ++++++++++++++++++++++++----------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app.js b/app.js index ff0e68c..cb66232 100644 --- a/app.js +++ b/app.js @@ -433,7 +433,6 @@ function onMessageAdded(from, message, avatar) { const $img = document.createElement('img'); $img.src = updatePortInUrl(avatar) || 'https://via.placeholder.com/40'; // Default to a placeholder image if avatar URL is not provided - console.log(updatePortInUrl(avatar)) $img.classList.add('avatar'); $div.appendChild($img); diff --git a/chatBot/includes/Client.js b/chatBot/includes/Client.js index 426b094..97f13fc 100644 --- a/chatBot/includes/Client.js +++ b/chatBot/includes/Client.js @@ -5,7 +5,7 @@ import b4a from "b4a"; class Client extends EventEmitter { constructor(botName) { super(); - if(!botName) return console.error("BotName is not defined!"); + if (!botName) return console.error("BotName is not defined!"); this.botName = botName; this.swarm = new Hyperswarm(); this.setupSwarm(); @@ -14,9 +14,10 @@ class Client extends EventEmitter { setupSwarm() { this.swarm.on('connection', (peer) => { peer.on('data', message => { - if(message.type === "message") this.emit('onMessage', peer, JSON.parse(message.toString())); - if(message.type === "icon") this.emit('onIcon', peer, JSON.parse(message.toString())); - if(message.type === "file") this.emit('onFile', peer, JSON.parse(message.toString())); + const messageObj = JSON.parse(message.toString()); + if (messageObj.type === "message") this.emit('onMessage', peer, messageObj); + if (messageObj.type === "icon") this.emit('onIcon', peer, messageObj); + if (messageObj.type === "file") this.emit('onFile', peer, messageObj); }); peer.on('error', e => { @@ -33,14 +34,14 @@ class Client extends EventEmitter { const peer = [peerId]; const peerTopics = [peerInfo.topics] - .filter(topics => topics) - .map(topics => topics.map(topic => b4a.toString(topic, 'hex'))); + .filter(topics => topics) + .map(topics => topics.map(topic => b4a.toString(topic, 'hex'))); }); }); } joinChatRoom(chatRoomID) { - this.discovery = this.swarm.join(Buffer.from(chatRoomID, 'hex'), {client: true, server: true}); + this.discovery = this.swarm.join(Buffer.from(chatRoomID, 'hex'), { client: true, server: true }); this.discovery.flushed().then(() => { console.log(`Bot ${this.botName} joined the chat room.`); this.emit('onBotJoinRoom'); @@ -50,16 +51,28 @@ class Client extends EventEmitter { sendMessage(roomPeers, message) { console.log('Bot name:', this.botName); const timestamp = Date.now(); // Generate timestamp - const peers = [...this.swarm.connections].filter(peer => roomPeers.includes(peer.remotePublicKey.toString('hex'))); // We remove all the peers that arent included in a room - const data = JSON.stringify({name: this.botName, message, timestamp}); // Include timestamp + const messageObj = { + type: 'message', // Add type field + name: this.botName, + message, + timestamp + }; + const data = JSON.stringify(messageObj); + const peers = [...this.swarm.connections].filter(peer => roomPeers.includes(peer.remotePublicKey.toString('hex'))); // We remove all the peers that aren't included in a room for (const peer of peers) peer.write(data); } sendMessageToAll(message) { console.log('Bot name:', this.botName); const timestamp = Date.now(); // Generate timestamp - const peers = [...this.swarm.connections] - const data = JSON.stringify({name: this.botName, message, timestamp}); // Include timestamp + const messageObj = { + type: 'message', // Add type field + name: this.botName, + message, + timestamp + }; + const data = JSON.stringify(messageObj); + const peers = [...this.swarm.connections]; for (const peer of peers) peer.write(data); } From 8d1ecc2c19378f954c45ac7da05b2b0a3d041bbd Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Mon, 10 Jun 2024 20:01:00 -0400 Subject: [PATCH 05/16] Adding in Room Aliases --- app.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index cb66232..57122c6 100644 --- a/app.js +++ b/app.js @@ -89,11 +89,13 @@ async function initialize() { // Update port in URLs config.userAvatar = updatePortInUrl(config.userAvatar); config.rooms.forEach(room => { - addRoomToListWithoutWritingToConfig(room); + room.alias = room.alias || truncateHash(room.topic); }); for (let user in registeredUsers) { registeredUsers[user] = updatePortInUrl(registeredUsers[user]); } + + renderRoomList(); // Render the room list with aliases } const registerDiv = document.querySelector('#register'); @@ -257,10 +259,14 @@ function addRoomToList(topic) { const roomItem = document.createElement('li'); roomItem.textContent = truncateHash(topic); roomItem.dataset.topic = topic; + + // Add double-click event listener for editing room name + roomItem.addEventListener('dblclick', () => enterEditMode(roomItem)); + roomItem.addEventListener('click', () => switchRoom(topic)); roomList.appendChild(roomItem); - config.rooms.push(topic); + config.rooms.push({ topic, alias: truncateHash(topic) }); writeConfigToFile("./config.json"); } @@ -269,10 +275,55 @@ function addRoomToListWithoutWritingToConfig(topic) { const roomItem = document.createElement('li'); roomItem.textContent = truncateHash(topic); roomItem.dataset.topic = topic; + + // Add double-click event listener for editing room name + roomItem.addEventListener('dblclick', () => enterEditMode(roomItem)); + roomItem.addEventListener('click', () => switchRoom(topic)); roomList.appendChild(roomItem); } +function enterEditMode(roomItem) { + const originalText = roomItem.textContent; + const topic = roomItem.dataset.topic; + roomItem.innerHTML = ''; + + const input = document.createElement('input'); + input.type = 'text'; + input.value = originalText; + roomItem.appendChild(input); + + input.focus(); + + input.addEventListener('blur', () => { + exitEditMode(roomItem, input, topic); + }); + + input.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + exitEditMode(roomItem, input, topic); + } else if (e.key === 'Escape') { + roomItem.textContent = originalText; + } + }); +} + +function exitEditMode(roomItem, input, topic) { + const newAlias = input.value.trim(); + if (newAlias) { + roomItem.textContent = newAlias; + + // Update the config with the new alias + const roomConfig = config.rooms.find(room => room.topic === topic); + if (roomConfig) { + roomConfig.alias = newAlias; + writeConfigToFile("./config.json"); + } + } else { + roomItem.textContent = truncateHash(topic); + } +} + function switchRoom(topic) { const topicBuffer = b4a.from(topic, 'hex'); joinSwarm(topicBuffer); @@ -286,7 +337,7 @@ function leaveRoom() { roomItem.remove(); } - config.rooms = config.rooms.filter(e => e !== topic); + config.rooms = config.rooms.filter(e => e.topic !== topic); writeConfigToFile("./config.json"); currentRoom.destroy(); @@ -510,4 +561,22 @@ function updatePortInUrl(url) { return urlObject.toString(); } +function renderRoomList() { + const roomList = document.querySelector('#room-list'); + roomList.innerHTML = ''; + + config.rooms.forEach(room => { + const roomItem = document.createElement('li'); + roomItem.textContent = room.alias || truncateHash(room.topic); + roomItem.dataset.topic = room.topic; + + roomItem.addEventListener('dblclick', () => enterEditMode(roomItem)); + roomItem.addEventListener('click', () => switchRoom(room.topic)); + roomList.appendChild(roomItem); + }); +} + +// Call this function when loading the rooms initially +renderRoomList(); + initialize(); From 3dcabac0b4cf691d6964eb8c622b260967f02cd8 Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Mon, 10 Jun 2024 20:28:16 -0400 Subject: [PATCH 06/16] Fix channel edit input size --- app.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app.js b/app.js index 57122c6..d4c06a8 100644 --- a/app.js +++ b/app.js @@ -291,6 +291,9 @@ function enterEditMode(roomItem) { const input = document.createElement('input'); input.type = 'text'; input.value = originalText; + input.style.maxWidth = '100%'; // Add this line to set the max width + input.style.boxSizing = 'border-box'; // Add this line to ensure padding and border are included in the width + roomItem.appendChild(input); input.focus(); From d0d408230a1cd38edcd62fdaa7a6cd5ea026240b Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Mon, 10 Jun 2024 21:08:44 -0400 Subject: [PATCH 07/16] Adding buttons instead of links for downloads --- app.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app.js b/app.js index d4c06a8..db71833 100644 --- a/app.js +++ b/app.js @@ -456,11 +456,15 @@ function addFileMessage(from, fileName, fileUrl, fileType, avatar) { $image.classList.add('attached-image'); $content.appendChild($image); } else { - const $fileLink = document.createElement('a'); - $fileLink.href = fileUrl; - $fileLink.textContent = `File: ${fileName}`; - $fileLink.download = fileName; - $content.appendChild($fileLink); + const $fileButton = document.createElement('button'); + $fileButton.textContent = `Download File: ${fileName}`; + $fileButton.onclick = function() { + const $fileLink = document.createElement('a'); + $fileLink.href = fileUrl; + $fileLink.download = fileName; + $fileLink.click(); + }; + $content.appendChild($fileButton); } $div.appendChild($content); From 6a0b05df86d725a47c7335901f3157c0b0550341 Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Mon, 10 Jun 2024 21:51:34 -0400 Subject: [PATCH 08/16] REFRACTOR: New connection logic, all saved rooms are connected on init. New Room tracking and message tracking with message history after switching. Better connection management --- app.js | 254 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 155 insertions(+), 99 deletions(-) diff --git a/app.js b/app.js index db71833..d1a2bdd 100644 --- a/app.js +++ b/app.js @@ -16,7 +16,7 @@ await drive.ready(); let swarm; let registeredUsers = JSON.parse(localStorage.getItem('registeredUsers')) || {}; let peerCount = 0; -let currentRoom = null; +let activeRooms = []; const eventEmitter = new EventEmitter(); // Define servePort at the top level @@ -29,7 +29,10 @@ let config = { rooms: [] }; -// Function to get a random port between 1337 and 2223 +// Store messages for each room +let messagesStore = {}; + +// Function to get a random port between 49152 and 65535 function getRandomPort() { return Math.floor(Math.random() * (65535 - 49152 + 1)) + 49152; } @@ -73,7 +76,10 @@ async function initialize() { toggleSetupBtn.addEventListener('click', toggleSetupView); } if (removeRoomBtn) { - removeRoomBtn.addEventListener('click', leaveRoom); + removeRoomBtn.addEventListener('click', () => { + const topic = document.querySelector('#chat-room-topic').innerText; + leaveRoom(topic); + }); } if (attachFileButton) { attachFileButton.addEventListener('click', () => fileInput.click()); @@ -85,7 +91,7 @@ async function initialize() { const configExists = fs.existsSync("./config.json"); if (configExists) { config = JSON.parse(fs.readFileSync("./config.json", 'utf8')); - console.log("Read config from file:", config) + console.log("Read config from file:", config); // Update port in URLs config.userAvatar = updatePortInUrl(config.userAvatar); config.rooms.forEach(room => { @@ -96,6 +102,12 @@ async function initialize() { } renderRoomList(); // Render the room list with aliases + + // Connect to all rooms on startup + for (const room of config.rooms) { + const topicBuffer = b4a.from(room.topic, 'hex'); + await joinSwarm(topicBuffer); + } } const registerDiv = document.querySelector('#register'); @@ -105,7 +117,7 @@ async function initialize() { eventEmitter.on('onMessage', async (messageObj) => { console.log('Received message:', messageObj); // Debugging log - + if (messageObj.type === 'icon') { const username = messageObj.username; if (messageObj.avatar) { @@ -120,12 +132,12 @@ async function initialize() { const fileBuffer = b4a.from(messageObj.file, 'base64'); await drive.put(`/files/${messageObj.fileName}`, fileBuffer); const fileUrl = `http://localhost:${servePort}/files/${messageObj.fileName}`; - addFileMessage(messageObj.name, messageObj.fileName, updatePortInUrl(fileUrl), messageObj.fileType, updatePortInUrl(messageObj.avatar)); + addFileMessage(messageObj.name, messageObj.fileName, updatePortInUrl(fileUrl), messageObj.fileType, updatePortInUrl(messageObj.avatar), messageObj.topic); } else { console.error('Received file message with missing file data or fileName:', messageObj); } } else if (messageObj.type === 'message') { - onMessageAdded(messageObj.name, messageObj.message, messageObj.avatar); + onMessageAdded(messageObj.name, messageObj.message, messageObj.avatar, messageObj.topic); } else { console.error('Received unknown message type:', messageObj); } @@ -223,7 +235,7 @@ async function createChatRoom() { const topicBuffer = crypto.randomBytes(32); const topic = b4a.toString(topicBuffer, 'hex'); addRoomToList(topic); - joinSwarm(topicBuffer); + await joinSwarm(topicBuffer); } async function joinChatRoom(e) { @@ -231,27 +243,21 @@ async function joinChatRoom(e) { const topicStr = document.querySelector('#join-chat-room-topic').value; const topicBuffer = b4a.from(topicStr, 'hex'); addRoomToList(topicStr); - joinSwarm(topicBuffer); + await joinSwarm(topicBuffer); } async function joinSwarm(topicBuffer) { - if (currentRoom) { - currentRoom.destroy(); - } - - document.querySelector('#setup').classList.add('hidden'); - document.querySelector('#loading').classList.remove('hidden'); - - const discovery = swarm.join(topicBuffer, { client: true, server: true }); - await discovery.flushed(); - const topic = b4a.toString(topicBuffer, 'hex'); - document.querySelector('#chat-room-topic').innerText = topic; // Set full topic here - document.querySelector('#loading').classList.add('hidden'); - document.querySelector('#chat').classList.remove('hidden'); + if (!activeRooms.some(room => room.topic === topic)) { + const discovery = swarm.join(topicBuffer, { client: true, server: true }); + await discovery.flushed(); - currentRoom = discovery; - clearMessages(); + activeRooms.push({ topic, discovery }); + + console.log('Joined room:', topic); // Debugging log + + renderMessagesForRoom(topic); + } } function addRoomToList(topic) { @@ -260,9 +266,7 @@ function addRoomToList(topic) { roomItem.textContent = truncateHash(topic); roomItem.dataset.topic = topic; - // Add double-click event listener for editing room name roomItem.addEventListener('dblclick', () => enterEditMode(roomItem)); - roomItem.addEventListener('click', () => switchRoom(topic)); roomList.appendChild(roomItem); @@ -270,19 +274,6 @@ function addRoomToList(topic) { writeConfigToFile("./config.json"); } -function addRoomToListWithoutWritingToConfig(topic) { - const roomList = document.querySelector('#room-list'); - const roomItem = document.createElement('li'); - roomItem.textContent = truncateHash(topic); - roomItem.dataset.topic = topic; - - // Add double-click event listener for editing room name - roomItem.addEventListener('dblclick', () => enterEditMode(roomItem)); - - roomItem.addEventListener('click', () => switchRoom(topic)); - roomList.appendChild(roomItem); -} - function enterEditMode(roomItem) { const originalText = roomItem.textContent; const topic = roomItem.dataset.topic; @@ -328,26 +319,38 @@ function exitEditMode(roomItem, input, topic) { } function switchRoom(topic) { - const topicBuffer = b4a.from(topic, 'hex'); - joinSwarm(topicBuffer); + console.log('Switching to room:', topic); // Debugging log + document.querySelector('#chat-room-topic').innerText = topic; // Set full topic here + clearMessages(); + renderMessagesForRoom(topic); + + // Show chat UI elements + document.querySelector('#chat').classList.remove('hidden'); + document.querySelector('#setup').classList.add('hidden'); } -function leaveRoom() { - if (currentRoom) { - const topic = b4a.toString(currentRoom.topic, 'hex'); - const roomItem = document.querySelector(`li[data-topic="${topic}"]`); - if (roomItem) { - roomItem.remove(); - } - - config.rooms = config.rooms.filter(e => e.topic !== topic); - writeConfigToFile("./config.json"); - - currentRoom.destroy(); - currentRoom = null; +function leaveRoom(topic) { + const roomIndex = activeRooms.findIndex(room => room.topic === topic); + if (roomIndex !== -1) { + const room = activeRooms[roomIndex]; + room.discovery.destroy(); + activeRooms.splice(roomIndex, 1); + } + + const roomItem = document.querySelector(`li[data-topic="${topic}"]`); + if (roomItem) { + roomItem.remove(); + } + + config.rooms = config.rooms.filter(e => e.topic !== topic); + writeConfigToFile("./config.json"); + + if (activeRooms.length > 0) { + switchRoom(activeRooms[0].topic); + } else { + document.querySelector('#chat').classList.add('hidden'); + document.querySelector('#setup').classList.remove('hidden'); } - document.querySelector('#chat').classList.add('hidden'); - document.querySelector('#setup').classList.remove('hidden'); } function sendMessage(e) { @@ -355,7 +358,11 @@ function sendMessage(e) { const message = document.querySelector('#message').value; document.querySelector('#message').value = ''; - onMessageAdded(config.userName, message, config.userAvatar); + const topic = document.querySelector('#chat-room-topic').innerText; + + console.log('Sending message:', message); // Debugging log + + onMessageAdded(config.userName, message, config.userAvatar, topic); let peersPublicKeys = []; peersPublicKeys.push([...swarm.connections].map(peer => peer.remotePublicKey.toString('hex'))); @@ -367,7 +374,7 @@ function sendMessage(e) { name: config.userName, message, avatar: config.userAvatar, - topic: b4a.toString(currentRoom.topic, 'hex'), + topic: topic, peers: peersPublicKeys, // Deprecated. To be deleted in future updates timestamp: Date.now(), readableTimestamp: new Date().toLocaleString(), // Added human-readable timestamp @@ -389,6 +396,8 @@ async function handleFileInput(event) { await drive.put(filePath, buffer); const fileUrl = `http://localhost:${servePort}${filePath}`; + const topic = document.querySelector('#chat-room-topic').innerText; + const fileMessage = { type: 'file', name: config.userName, @@ -396,6 +405,7 @@ async function handleFileInput(event) { file: b4a.toString(buffer, 'base64'), fileType: file.type, avatar: updatePortInUrl(config.userAvatar), + topic: topic }; console.log('Sending file message:', fileMessage); // Debugging log @@ -405,7 +415,7 @@ async function handleFileInput(event) { peer.write(JSON.stringify(fileMessage)); } - addFileMessage(config.userName, file.name, fileUrl, file.type, config.userAvatar); + addFileMessage(config.userName, file.name, fileUrl, file.type, config.userAvatar, topic); }; reader.readAsArrayBuffer(file); } @@ -420,7 +430,7 @@ function sendFileMessage(name, fileUrl, fileType, avatar) { fileUrl, fileType, avatar, - topic: b4a.toString(currentRoom.topic, 'hex'), + topic: document.querySelector('#chat-room-topic').innerText, timestamp: Date.now(), }); @@ -429,10 +439,11 @@ function sendFileMessage(name, fileUrl, fileType, avatar) { peer.write(messageObj); } - addFileMessage(name, fileName, fileUrl, fileType, avatar); + addFileMessage(name, fileName, fileUrl, fileType, avatar, document.querySelector('#chat-room-topic').innerText); } -function addFileMessage(from, fileName, fileUrl, fileType, avatar) { +function addFileMessage(from, fileName, fileUrl, fileType, avatar, topic) { + console.log('Adding file message:', { from, fileName, fileUrl, fileType, avatar, topic }); // Debugging log const $div = document.createElement('div'); $div.classList.add('message'); @@ -459,17 +470,24 @@ function addFileMessage(from, fileName, fileUrl, fileType, avatar) { const $fileButton = document.createElement('button'); $fileButton.textContent = `Download File: ${fileName}`; $fileButton.onclick = function() { - const $fileLink = document.createElement('a'); - $fileLink.href = fileUrl; - $fileLink.download = fileName; - $fileLink.click(); + const $fileLink = document.createElement('a'); + $fileLink.href = fileUrl; + $fileLink.download = fileName; + $fileLink.click(); }; $content.appendChild($fileButton); } $div.appendChild($content); - document.querySelector('#messages').appendChild($div); - scrollToBottom(); + + // Only render the message if it's for the current room + const currentTopic = document.querySelector('#chat-room-topic').innerText; + if (currentTopic === topic) { + document.querySelector('#messages').appendChild($div); + scrollToBottom(); + } else { + console.log(`Message for topic ${topic} not rendered because current topic is ${currentTopic}`); // Debugging log + } } function updatePeerCount() { @@ -484,46 +502,61 @@ function scrollToBottom() { container.scrollTop = container.scrollHeight; } -function onMessageAdded(from, message, avatar) { - const $div = document.createElement('div'); - $div.classList.add('message'); - - const $img = document.createElement('img'); +function onMessageAdded(from, message, avatar, topic) { + console.log('Adding message:', { from, message, avatar, topic }); // Debugging log + const messageObj = { + from, + message, + avatar + }; - $img.src = updatePortInUrl(avatar) || 'https://via.placeholder.com/40'; // Default to a placeholder image if avatar URL is not provided - $img.classList.add('avatar'); - $div.appendChild($img); + // Add the message to the store + addMessageToStore(topic, messageObj); - const $content = document.createElement('div'); - $content.classList.add('message-content'); + // Only render messages for the current room + const currentTopic = document.querySelector('#chat-room-topic').innerText; + if (currentTopic === topic) { + const $div = document.createElement('div'); + $div.classList.add('message'); - const $header = document.createElement('div'); - $header.classList.add('message-header'); - $header.textContent = from; + const $img = document.createElement('img'); + $img.src = updatePortInUrl(avatar) || 'https://via.placeholder.com/40'; // Default to a placeholder image if avatar URL is not provided + $img.classList.add('avatar'); + $div.appendChild($img); - const $text = document.createElement('div'); - $text.classList.add('message-text'); + const $content = document.createElement('div'); + $content.classList.add('message-content'); - const md = window.markdownit({ - highlight: function (str, lang) { - if (lang && hljs.getLanguage(lang)) { - try { - return hljs.highlight(str, { language: lang }).value; - } catch (__) {} + const $header = document.createElement('div'); + $header.classList.add('message-header'); + $header.textContent = from; + + const $text = document.createElement('div'); + $text.classList.add('message-text'); + + const md = window.markdownit({ + highlight: function (str, lang) { + if (lang && hljs.getLanguage(lang)) { + try { + return hljs.highlight(str, { language: lang }).value; + } catch (__) {} + } + return ''; // use external default escaping } - return ''; // use external default escaping - } - }); + }); - const markdownContent = md.render(message); - $text.innerHTML = markdownContent; + const markdownContent = md.render(message); + $text.innerHTML = markdownContent; - $content.appendChild($header); - $content.appendChild($text); - $div.appendChild($content); + $content.appendChild($header); + $content.appendChild($text); + $div.appendChild($content); - document.querySelector('#messages').appendChild($div); - scrollToBottom(); + document.querySelector('#messages').appendChild($div); + scrollToBottom(); + } else { + console.log(`Message for topic ${topic} not rendered because current topic is ${currentTopic}`); // Debugging log + } } function truncateHash(hash) { @@ -583,6 +616,29 @@ function renderRoomList() { }); } +function renderMessagesForRoom(topic) { + console.log('Rendering messages for room:', topic); // Debugging log + // Clear the message area + clearMessages(); + + // Fetch and render messages for the selected room + const messages = getMessagesForRoom(topic); + messages.forEach(message => { + onMessageAdded(message.from, message.message, message.avatar, topic); + }); +} + +function getMessagesForRoom(topic) { + return messagesStore[topic] || []; +} + +function addMessageToStore(topic, messageObj) { + if (!messagesStore[topic]) { + messagesStore[topic] = []; + } + messagesStore[topic].push(messageObj); +} + // Call this function when loading the rooms initially renderRoomList(); From 157c8af4f4417e43c22c60d501d0600da5ec8a7d Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Mon, 10 Jun 2024 22:04:57 -0400 Subject: [PATCH 09/16] Update bot Client to work with the new connection logic --- chatBot/includes/Client.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/chatBot/includes/Client.js b/chatBot/includes/Client.js index 97f13fc..7c939c0 100644 --- a/chatBot/includes/Client.js +++ b/chatBot/includes/Client.js @@ -1,5 +1,5 @@ import Hyperswarm from 'hyperswarm'; -import EventEmitter from 'node:events' +import EventEmitter from 'node:events'; import b4a from "b4a"; class Client extends EventEmitter { @@ -8,6 +8,7 @@ class Client extends EventEmitter { if (!botName) return console.error("BotName is not defined!"); this.botName = botName; this.swarm = new Hyperswarm(); + this.currentTopic = null; // Store the current topic this.setupSwarm(); } @@ -15,7 +16,10 @@ class Client extends EventEmitter { this.swarm.on('connection', (peer) => { peer.on('data', message => { const messageObj = JSON.parse(message.toString()); - if (messageObj.type === "message") this.emit('onMessage', peer, messageObj); + if (messageObj.type === "message") { + this.currentTopic = messageObj.topic; // Capture the topic from incoming messages + this.emit('onMessage', peer, messageObj); + } if (messageObj.type === "icon") this.emit('onIcon', peer, messageObj); if (messageObj.type === "file") this.emit('onFile', peer, messageObj); }); @@ -30,8 +34,6 @@ class Client extends EventEmitter { console.log(`Connections count: ${this.swarm.connections.size} / Peers count: ${this.swarm.peers.size}`); this.swarm.peers.forEach((peerInfo, peerId) => { - // Please do not try to understand what is going on here. I have no idea anyway. But it surprisingly works - const peer = [peerId]; const peerTopics = [peerInfo.topics] .filter(topics => topics) @@ -41,6 +43,7 @@ class Client extends EventEmitter { } joinChatRoom(chatRoomID) { + this.currentTopic = chatRoomID; // Store the current topic this.discovery = this.swarm.join(Buffer.from(chatRoomID, 'hex'), { client: true, server: true }); this.discovery.flushed().then(() => { console.log(`Bot ${this.botName} joined the chat room.`); @@ -50,26 +53,28 @@ class Client extends EventEmitter { sendMessage(roomPeers, message) { console.log('Bot name:', this.botName); - const timestamp = Date.now(); // Generate timestamp + const timestamp = Date.now(); const messageObj = { - type: 'message', // Add type field + type: 'message', name: this.botName, message, - timestamp + timestamp, + topic: this.currentTopic // Include the current topic }; const data = JSON.stringify(messageObj); - const peers = [...this.swarm.connections].filter(peer => roomPeers.includes(peer.remotePublicKey.toString('hex'))); // We remove all the peers that aren't included in a room + const peers = [...this.swarm.connections].filter(peer => roomPeers.includes(peer.remotePublicKey.toString('hex'))); for (const peer of peers) peer.write(data); } sendMessageToAll(message) { console.log('Bot name:', this.botName); - const timestamp = Date.now(); // Generate timestamp + const timestamp = Date.now(); const messageObj = { - type: 'message', // Add type field + type: 'message', name: this.botName, message, - timestamp + timestamp, + topic: this.currentTopic // Include the current topic }; const data = JSON.stringify(messageObj); const peers = [...this.swarm.connections]; From e57db27a1341b7b39417024711f295b403e7558c Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Mon, 10 Jun 2024 22:20:36 -0400 Subject: [PATCH 10/16] Fixing bot --- chatBot/includes/Client.js | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/chatBot/includes/Client.js b/chatBot/includes/Client.js index 7c939c0..8fe0b5e 100644 --- a/chatBot/includes/Client.js +++ b/chatBot/includes/Client.js @@ -8,7 +8,8 @@ class Client extends EventEmitter { if (!botName) return console.error("BotName is not defined!"); this.botName = botName; this.swarm = new Hyperswarm(); - this.currentTopic = null; // Store the current topic + this.joinedRooms = new Set(); // Track the rooms the bot has joined + this.currentTopic = null; // Track the current topic this.setupSwarm(); } @@ -16,12 +17,12 @@ class Client extends EventEmitter { this.swarm.on('connection', (peer) => { peer.on('data', message => { const messageObj = JSON.parse(message.toString()); - if (messageObj.type === "message") { - this.currentTopic = messageObj.topic; // Capture the topic from incoming messages - this.emit('onMessage', peer, messageObj); + if (this.joinedRooms.has(messageObj.topic)) { // Process message only if it is from a joined room + this.currentTopic = messageObj.topic; // Set the current topic from the incoming message + if (messageObj.type === "message") this.emit('onMessage', peer, messageObj); + if (messageObj.type === "icon") this.emit('onIcon', peer, messageObj); + if (messageObj.type === "file") this.emit('onFile', peer, messageObj); } - if (messageObj.type === "icon") this.emit('onIcon', peer, messageObj); - if (messageObj.type === "file") this.emit('onFile', peer, messageObj); }); peer.on('error', e => { @@ -32,17 +33,11 @@ class Client extends EventEmitter { this.swarm.on('update', () => { console.log(`Connections count: ${this.swarm.connections.size} / Peers count: ${this.swarm.peers.size}`); - - this.swarm.peers.forEach((peerInfo, peerId) => { - const peer = [peerId]; - const peerTopics = [peerInfo.topics] - .filter(topics => topics) - .map(topics => topics.map(topic => b4a.toString(topic, 'hex'))); - }); }); } joinChatRoom(chatRoomID) { + this.joinedRooms.add(chatRoomID); // Add the room to the list of joined rooms this.currentTopic = chatRoomID; // Store the current topic this.discovery = this.swarm.join(Buffer.from(chatRoomID, 'hex'), { client: true, server: true }); this.discovery.flushed().then(() => { From 9aedd9fda4979ab91b789803b0295d10bec8145b Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Tue, 11 Jun 2024 00:18:27 -0400 Subject: [PATCH 11/16] update peer count per topic --- app.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/app.js b/app.js index d1a2bdd..4f2f9ac 100644 --- a/app.js +++ b/app.js @@ -15,7 +15,7 @@ await drive.ready(); let swarm; let registeredUsers = JSON.parse(localStorage.getItem('registeredUsers')) || {}; -let peerCount = 0; +let peerCounts = {}; // Change peerCount to an object let activeRooms = []; const eventEmitter = new EventEmitter(); @@ -144,9 +144,13 @@ async function initialize() { }); swarm.on('connection', async (connection, info) => { - peerCount++; - updatePeerCount(); - console.log('Peer connected, current peer count:', peerCount); + const topic = info.topics[0].toString('hex'); + if (!peerCounts[topic]) { + peerCounts[topic] = 0; + } + peerCounts[topic]++; + updatePeerCount(topic); + console.log('Peer connected, current peer count for topic', topic, ':', peerCounts[topic]); // Send the current user's icon to the new peer const iconBuffer = await drive.get(`/icons/${config.userName}.png`); @@ -165,9 +169,9 @@ async function initialize() { }); connection.on('close', () => { - peerCount--; - updatePeerCount(); - console.log('Peer disconnected, current peer count:', peerCount); + peerCounts[topic]--; + updatePeerCount(topic); + console.log('Peer disconnected, current peer count for topic', topic, ':', peerCounts[topic]); }); }); @@ -321,6 +325,7 @@ function exitEditMode(roomItem, input, topic) { function switchRoom(topic) { console.log('Switching to room:', topic); // Debugging log document.querySelector('#chat-room-topic').innerText = topic; // Set full topic here + updatePeerCount(topic); clearMessages(); renderMessagesForRoom(topic); @@ -490,10 +495,10 @@ function addFileMessage(from, fileName, fileUrl, fileType, avatar, topic) { } } -function updatePeerCount() { +function updatePeerCount(topic) { const peerCountElement = document.querySelector('#peers-count'); if (peerCountElement) { - peerCountElement.textContent = peerCount; // Display the actual peer count + peerCountElement.textContent = peerCounts[topic] || 0; // Display the peer count for the specific topic } } From 7ec290116589f6a97dd99cd62e7f08e4604e3197 Mon Sep 17 00:00:00 2001 From: snxraven Date: Tue, 11 Jun 2024 04:40:01 +0000 Subject: [PATCH 12/16] revert 9aedd9fda4979ab91b789803b0295d10bec8145b revert update peer count per topic --- app.js | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/app.js b/app.js index 4f2f9ac..d1a2bdd 100644 --- a/app.js +++ b/app.js @@ -15,7 +15,7 @@ await drive.ready(); let swarm; let registeredUsers = JSON.parse(localStorage.getItem('registeredUsers')) || {}; -let peerCounts = {}; // Change peerCount to an object +let peerCount = 0; let activeRooms = []; const eventEmitter = new EventEmitter(); @@ -144,13 +144,9 @@ async function initialize() { }); swarm.on('connection', async (connection, info) => { - const topic = info.topics[0].toString('hex'); - if (!peerCounts[topic]) { - peerCounts[topic] = 0; - } - peerCounts[topic]++; - updatePeerCount(topic); - console.log('Peer connected, current peer count for topic', topic, ':', peerCounts[topic]); + peerCount++; + updatePeerCount(); + console.log('Peer connected, current peer count:', peerCount); // Send the current user's icon to the new peer const iconBuffer = await drive.get(`/icons/${config.userName}.png`); @@ -169,9 +165,9 @@ async function initialize() { }); connection.on('close', () => { - peerCounts[topic]--; - updatePeerCount(topic); - console.log('Peer disconnected, current peer count for topic', topic, ':', peerCounts[topic]); + peerCount--; + updatePeerCount(); + console.log('Peer disconnected, current peer count:', peerCount); }); }); @@ -325,7 +321,6 @@ function exitEditMode(roomItem, input, topic) { function switchRoom(topic) { console.log('Switching to room:', topic); // Debugging log document.querySelector('#chat-room-topic').innerText = topic; // Set full topic here - updatePeerCount(topic); clearMessages(); renderMessagesForRoom(topic); @@ -495,10 +490,10 @@ function addFileMessage(from, fileName, fileUrl, fileType, avatar, topic) { } } -function updatePeerCount(topic) { +function updatePeerCount() { const peerCountElement = document.querySelector('#peers-count'); if (peerCountElement) { - peerCountElement.textContent = peerCounts[topic] || 0; // Display the peer count for the specific topic + peerCountElement.textContent = peerCount; // Display the actual peer count } } From 6eab069cc59daf371f3104d59138800bc59a19f5 Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Tue, 11 Jun 2024 01:12:01 -0400 Subject: [PATCH 13/16] Move Peer Count to Title Bar | Make the count global to all rooms --- index.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.html b/index.html index f1b1e9e..4790ea1 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,7 @@
-
LinkUp
+
LinkUp | Peers: 0
- Peers: 0
From 7a74d3fc4d17b4ef6d2d84b04b1293ef034abfad Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Tue, 11 Jun 2024 02:52:53 -0400 Subject: [PATCH 14/16] Add room names to room view header, add a copy topic button and remove the display of the topics --- app.js | 28 ++++++++++++++++++++- index.html | 74 ++++++++++++++++++++++++++++++++++++++++++++++++------ style.css | 21 ++++++++++++++++ 3 files changed, 114 insertions(+), 9 deletions(-) diff --git a/app.js b/app.js index d1a2bdd..fa93d67 100644 --- a/app.js +++ b/app.js @@ -313,6 +313,15 @@ function exitEditMode(roomItem, input, topic) { roomConfig.alias = newAlias; writeConfigToFile("./config.json"); } + + // Check if the edited room is the current room in view + const currentTopic = document.querySelector('#chat-room-topic').innerText; + if (currentTopic === topic) { + const chatRoomName = document.querySelector('#chat-room-name'); + if (chatRoomName) { + chatRoomName.innerText = newAlias; + } + } } else { roomItem.textContent = truncateHash(topic); } @@ -320,7 +329,24 @@ function exitEditMode(roomItem, input, topic) { function switchRoom(topic) { console.log('Switching to room:', topic); // Debugging log - document.querySelector('#chat-room-topic').innerText = topic; // Set full topic here + const chatRoomTopic = document.querySelector('#chat-room-topic'); + const chatRoomName = document.querySelector('#chat-room-name'); + + if (chatRoomTopic) { + chatRoomTopic.innerText = topic; // Set full topic here + } else { + console.error('Element #chat-room-topic not found'); + } + + if (chatRoomName) { + // Update the room name in the header + const room = config.rooms.find(r => r.topic === topic); + const roomName = room ? room.alias : truncateHash(topic); + chatRoomName.innerText = roomName; + } else { + console.error('Element #chat-room-name not found'); + } + clearMessages(); renderMessagesForRoom(topic); diff --git a/index.html b/index.html index 4790ea1..18781d1 100644 --- a/index.html +++ b/index.html @@ -46,10 +46,9 @@