forked from snxraven/LinkUp-P2P-Chat
Trying to merge is so hard
This commit is contained in:
commit
5bb5e63105
3
.idea/.gitignore
vendored
Normal file
3
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
12
.idea/LinkUp-P2P-Chat.iml
Normal file
12
.idea/LinkUp-P2P-Chat.iml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/misc.xml
Normal file
6
.idea/misc.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="FLOW" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/LinkUp-P2P-Chat.iml" filepath="$PROJECT_DIR$/.idea/LinkUp-P2P-Chat.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
340
app.js
340
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,40 +76,38 @@ 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());
|
||||
}
|
||||
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");
|
||||
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 => {
|
||||
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
|
||||
|
||||
// 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');
|
||||
@ -115,15 +116,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');
|
||||
if (messageObj.avatar) {
|
||||
const avatarBuffer = b4a.from(messageObj.avatar, 'base64');
|
||||
await drive.put(`/icons/${username}.png`, avatarBuffer);
|
||||
updateIcon(username, avatarBuffer);
|
||||
} else if (messageObj.type === 'file') {
|
||||
addFileMessage(messageObj.name, messageObj.fileName, messageObj.fileUrl, messageObj.fileType, messageObj.avatar);
|
||||
} else {
|
||||
onMessageAdded(messageObj.name, messageObj.message, messageObj.avatar);
|
||||
console.error('Received icon message with missing avatar data:', messageObj);
|
||||
}
|
||||
} else if (messageObj.type === 'file') {
|
||||
if (messageObj.file && messageObj.fileName) {
|
||||
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), 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, messageObj.topic);
|
||||
} else {
|
||||
console.error('Received unknown message type:', messageObj);
|
||||
}
|
||||
});
|
||||
|
||||
@ -138,7 +154,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);
|
||||
}
|
||||
@ -162,6 +178,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) {
|
||||
@ -214,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) {
|
||||
@ -222,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 topic = b4a.toString(topicBuffer, 'hex');
|
||||
if (!activeRooms.some(room => room.topic === topic)) {
|
||||
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');
|
||||
activeRooms.push({ topic, discovery });
|
||||
|
||||
currentRoom = discovery;
|
||||
clearMessages();
|
||||
console.log('Joined room:', topic); // Debugging log
|
||||
|
||||
renderMessagesForRoom(topic);
|
||||
}
|
||||
}
|
||||
|
||||
function addRoomToList(topic) {
|
||||
@ -250,51 +265,130 @@ function addRoomToList(topic) {
|
||||
const roomItem = document.createElement('li');
|
||||
roomItem.textContent = truncateHash(topic);
|
||||
roomItem.dataset.topic = topic;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
function addRoomToListWithoutWritingToConfig(topic) {
|
||||
const roomList = document.querySelector('#room-list');
|
||||
const roomItem = document.createElement('li');
|
||||
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;
|
||||
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();
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
// 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);
|
||||
roomItem.dataset.topic = topic;
|
||||
roomItem.addEventListener('click', () => switchRoom(topic));
|
||||
roomList.appendChild(roomItem);
|
||||
}
|
||||
}
|
||||
|
||||
function switchRoom(topic) {
|
||||
const topicBuffer = b4a.from(topic, 'hex');
|
||||
joinSwarm(topicBuffer);
|
||||
console.log('Switching to room:', topic); // Debugging log
|
||||
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);
|
||||
|
||||
// Show chat UI elements
|
||||
document.querySelector('#chat').classList.remove('hidden');
|
||||
document.querySelector('#setup').classList.add('hidden');
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
config.rooms = config.rooms.filter(e => e.topic !== topic);
|
||||
writeConfigToFile("./config.json");
|
||||
|
||||
currentRoom.destroy();
|
||||
currentRoom = null;
|
||||
}
|
||||
if (activeRooms.length > 0) {
|
||||
switchRoom(activeRooms[0].topic);
|
||||
} else {
|
||||
document.querySelector('#chat').classList.add('hidden');
|
||||
document.querySelector('#setup').classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessage(e) {
|
||||
e.preventDefault();
|
||||
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')));
|
||||
@ -306,7 +400,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
|
||||
@ -318,6 +412,41 @@ 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 topic = document.querySelector('#chat-room-topic').innerText;
|
||||
|
||||
const fileMessage = {
|
||||
type: 'file',
|
||||
name: config.userName,
|
||||
fileName: file.name,
|
||||
file: b4a.toString(buffer, 'base64'),
|
||||
fileType: file.type,
|
||||
avatar: updatePortInUrl(config.userAvatar),
|
||||
topic: topic
|
||||
};
|
||||
|
||||
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, topic);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
}
|
||||
|
||||
function sendFileMessage(name, fileUrl, fileType, avatar) {
|
||||
const fileName = fileUrl.split('/').pop();
|
||||
const messageObj = JSON.stringify({
|
||||
@ -327,7 +456,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(),
|
||||
});
|
||||
|
||||
@ -336,10 +465,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');
|
||||
|
||||
@ -363,16 +493,27 @@ function addFileMessage(from, fileName, fileUrl, fileType, avatar) {
|
||||
$image.classList.add('attached-image');
|
||||
$content.appendChild($image);
|
||||
} else {
|
||||
const $fileButton = document.createElement('button');
|
||||
$fileButton.textContent = `Download File: ${fileName}`;
|
||||
$fileButton.onclick = function() {
|
||||
const $fileLink = document.createElement('a');
|
||||
$fileLink.href = fileUrl;
|
||||
$fileLink.textContent = `File: ${fileName}`;
|
||||
$fileLink.download = fileName;
|
||||
$content.appendChild($fileLink);
|
||||
$fileLink.click();
|
||||
};
|
||||
$content.appendChild($fileButton);
|
||||
}
|
||||
|
||||
$div.appendChild($content);
|
||||
|
||||
// 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() {
|
||||
@ -387,14 +528,25 @@ function scrollToBottom() {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
function onMessageAdded(from, message, avatar) {
|
||||
function onMessageAdded(from, message, avatar, topic) {
|
||||
console.log('Adding message:', { from, message, avatar, topic }); // Debugging log
|
||||
const messageObj = {
|
||||
from,
|
||||
message,
|
||||
avatar
|
||||
};
|
||||
|
||||
// Add the message to the store
|
||||
addMessageToStore(topic, messageObj);
|
||||
|
||||
// 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 $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);
|
||||
|
||||
@ -408,7 +560,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;
|
||||
|
||||
@ -418,6 +580,9 @@ function onMessageAdded(from, message, avatar) {
|
||||
|
||||
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) {
|
||||
@ -429,7 +594,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");
|
||||
@ -462,4 +627,45 @@ 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);
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
initialize();
|
@ -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,15 +8,21 @@ class Client extends EventEmitter {
|
||||
if(!botName) return console.error("Bot Name is not defined!");
|
||||
this.botName = botName;
|
||||
this.swarm = new Hyperswarm();
|
||||
this.joinedRooms = new Set(); // Track the rooms the bot has joined
|
||||
this.currentTopic = null; // Track the current topic
|
||||
this.setupSwarm();
|
||||
}
|
||||
|
||||
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 (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);
|
||||
}
|
||||
});
|
||||
|
||||
peer.on('error', e => {
|
||||
@ -27,19 +33,12 @@ 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) => {
|
||||
// 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)
|
||||
.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(() => {
|
||||
console.log(`Bot ${this.botName} joined the chat room.`);
|
||||
@ -49,17 +48,31 @@ 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 timestamp = Date.now();
|
||||
const messageObj = {
|
||||
type: 'message',
|
||||
name: this.botName,
|
||||
message,
|
||||
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')));
|
||||
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 timestamp = Date.now();
|
||||
const messageObj = {
|
||||
type: 'message',
|
||||
name: this.botName,
|
||||
message,
|
||||
timestamp,
|
||||
topic: this.currentTopic // Include the current topic
|
||||
};
|
||||
const data = JSON.stringify(messageObj);
|
||||
const peers = [...this.swarm.connections];
|
||||
for (const peer of peers) peer.write(data);
|
||||
}
|
||||
|
||||
|
59
index.html
59
index.html
@ -7,11 +7,13 @@
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<script type="module" src="./app.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/atom-one-dark.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<pear-ctrl></pear-ctrl>
|
||||
<div id="linkup-text">LinkUp</div>
|
||||
<div id="linkup-text">LinkUp | Peers: <span id="peers-count">0</span></div>
|
||||
</header>
|
||||
<main>
|
||||
<div id="sidebar">
|
||||
@ -44,11 +46,9 @@
|
||||
<div id="chat" class="hidden">
|
||||
<div id="header">
|
||||
<div id="details">
|
||||
<div>
|
||||
Topic: <span id="chat-room-topic"></span>
|
||||
</div>
|
||||
<div>
|
||||
Peers: <span id="peers-count">0</span>
|
||||
<div style="display: inline;">
|
||||
<strong><span id="chat-room-name"></span></strong> | <a href="#" id="copy-topic-link" class="mini-button">Copy Topic</a>
|
||||
<span id="chat-room-topic" style="display: none;"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="user-list">
|
||||
@ -72,12 +72,59 @@
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const messageInput = document.getElementById('message');
|
||||
const copyTopicLink = document.getElementById('copy-topic-link');
|
||||
const chatRoomTopic = document.getElementById('chat-room-topic');
|
||||
|
||||
if (messageInput) {
|
||||
messageInput.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
document.getElementById('message-form').dispatchEvent(new Event('submit'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (copyTopicLink) {
|
||||
copyTopicLink.addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
if (chatRoomTopic) {
|
||||
const topic = chatRoomTopic.innerText;
|
||||
navigator.clipboard.writeText(topic).then(() => {
|
||||
alert('Topic copied to clipboard!');
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy topic:', err);
|
||||
});
|
||||
} else {
|
||||
console.error('Element #chat-room-topic not found');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const switchRoom = (topic) => {
|
||||
console.log('Switching to room:', topic); // Debugging log
|
||||
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');
|
||||
}
|
||||
|
||||
// Show chat UI elements
|
||||
document.querySelector('#chat').classList.remove('hidden');
|
||||
document.querySelector('#setup').classList.add('hidden');
|
||||
};
|
||||
|
||||
const roomList = document.querySelector('#room-list');
|
||||
if (roomList) {
|
||||
roomList.addEventListener('click', function(event) {
|
||||
const roomItem = event.target.closest('li');
|
||||
if (roomItem) {
|
||||
switchRoom(roomItem.dataset.topic);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
21
style.css
21
style.css
@ -12,6 +12,27 @@ body {
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.mini-button {
|
||||
display: inline-block;
|
||||
padding: 3px 7px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
background-color: #7289da;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mini-button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
pear-ctrl[data-platform="darwin"] { float: right; margin-top: 4px; }
|
||||
|
||||
pear-ctrl {
|
||||
|
Loading…
Reference in New Issue
Block a user