LinkUp-P2P-Chat/chatBot/includes/Client.js

212 lines
7.4 KiB
JavaScript
Raw Normal View History

import path from 'path';
2024-06-03 19:23:14 -04:00
import Hyperswarm from 'hyperswarm';
import EventEmitter from 'node:events';
import b4a from "b4a";
import TextMessage from "./message/TextMessage.js";
import FileMessage from "./message/FileMessage.js";
import AudioMessage from "./message/AudioMessage.js";
import Message from "./message/Message.js";
import IconMessage from "./message/IconMessage.js";
2024-06-14 15:09:54 -04:00
import Corestore from 'corestore';
import Hyperdrive from 'hyperdrive';
import fs from 'fs';
import ServeDrive from 'serve-drive';
2024-06-03 19:23:14 -04:00
class Client extends EventEmitter {
constructor(botName) {
super();
2024-06-11 17:40:02 -04:00
if (!botName) return console.error("Bot Name is not defined!");
2024-06-03 19:23:14 -04:00
this.botName = botName;
this.swarm = new Hyperswarm();
2024-06-10 22:20:36 -04:00
this.joinedRooms = new Set(); // Track the rooms the bot has joined
this.currentTopic = null; // Track the current topic
2024-06-14 15:09:54 -04:00
// Initialize Corestore and Hyperdrive
this.storagePath = './storage/';
this.store = new Corestore(this.storagePath);
this.drive = new Hyperdrive(this.store);
// Initialize ServeDrive
this.servePort = null;
this.initializeServeDrive();
2024-06-03 19:23:14 -04:00
this.setupSwarm();
process.on('exit', () => {
2024-06-14 04:35:54 -04:00
console.log('EXIT signal received. Shutting down HyperSwarm...');
this.destroy();
});
2024-06-14 04:35:54 -04:00
2024-06-14 04:43:27 -04:00
process.on('SIGTERM', async () => {
2024-06-14 04:35:54 -04:00
console.log('SIGTERM signal received. Shutting down HyperSwarm...');
2024-06-14 15:09:54 -04:00
await this.destroy();
2024-06-14 04:43:27 -04:00
console.log('HyperSwarm was shut down. Exiting the process with exit code 0.');
process.exit(0);
2024-06-14 04:35:54 -04:00
});
2024-06-14 04:43:27 -04:00
process.on('SIGINT', async () => {
2024-06-14 04:35:54 -04:00
console.log('SIGINT signal received. Shutting down HyperSwarm...');
2024-06-14 15:09:54 -04:00
await this.destroy();
2024-06-14 04:43:27 -04:00
console.log('HyperSwarm was shut down. Exiting the process with exit code 0.');
process.exit(0);
2024-06-14 04:35:54 -04:00
});
2024-06-03 19:23:14 -04:00
}
2024-06-14 15:09:54 -04:00
async initializeServeDrive() {
try {
this.servePort = this.getRandomPort();
const serve = new ServeDrive({
port: this.servePort,
get: ({ key, filename, version }) => this.drive
});
await serve.ready();
console.log('ServeDrive listening on port:', this.servePort);
} catch (error) {
console.error('Error initializing ServeDrive:', error);
}
}
getRandomPort() {
return Math.floor(Math.random() * (65535 - 49152 + 1)) + 49152;
}
async fetchAvatar(filePath) {
try {
await this.drive.ready();
const iconBuffer = fs.readFileSync(filePath);
await this.drive.put(`/icons/${this.botName}.png`, iconBuffer);
this.botAvatar = `http://localhost:${this.servePort}/icons/${this.botName}.png`;
// Cache the icon message
this.iconMessage = IconMessage.new(this, iconBuffer);
} catch (error) {
console.error('Error fetching avatar:', error);
}
2024-06-14 12:49:26 -04:00
}
2024-06-03 19:23:14 -04:00
setupSwarm() {
this.swarm.on('connection', (peer) => {
2024-06-14 15:09:54 -04:00
// Send the cached icon message to the new peer
if (this.iconMessage) {
peer.write(this.iconMessage.toJsonString());
}
peer.on('data', async message => {
2024-06-10 19:49:46 -04:00
const messageObj = JSON.parse(message.toString());
2024-06-10 22:20:36 -04:00
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
const msgType = messageObj.type;
const peerName = messageObj.name; // Changed from name to userName
const peerAvatar = messageObj.avatar;
const timestamp = messageObj.timestamp;
if (msgType === "message")
this.emit('onMessage', peer, new TextMessage(peerName, peerAvatar, this.currentTopic, timestamp, messageObj.message));
if (msgType === "file") {
const fileBuffer = await this.drive.get(`/files/${messageObj.fileName}`);
this.emit('onFile', peer, new FileMessage(peerName, peerAvatar, this.currentTopic, timestamp, messageObj.fileName, `http://localhost:${this.servePort}/files/${messageObj.fileName}`, messageObj.fileType, messageObj.fileData));
}
2024-06-14 09:43:17 -04:00
if (msgType === "icon")
this.emit('onIcon', peer, new IconMessage(peerName, peerAvatar, timestamp));
if (msgType === "audio") {
const audioBuffer = await this.drive.get(`/audio/${messageObj.audioName}`);
this.emit('onAudio', peer, new AudioMessage(peerName, peerAvatar, this.currentTopic, timestamp, `http://localhost:${this.servePort}/audio/${messageObj.audioName}`, messageObj.audioType, messageObj.audioData));
}
}
});
2024-06-08 16:29:51 -04:00
peer.on('error', e => {
this.emit('onError', e);
2024-06-14 15:09:54 -04:00
console.error(`Connection error: ${e}`);
2024-06-08 16:29:51 -04:00
});
2024-06-03 19:23:14 -04:00
});
this.swarm.on('update', () => {
2024-06-10 13:57:32 -04:00
console.log(`Connections count: ${this.swarm.connections.size} / Peers count: ${this.swarm.peers.size}`);
2024-06-03 19:23:14 -04:00
});
}
joinChatRoom(chatRoomID) {
2024-06-11 17:40:02 -04:00
if (!chatRoomID || typeof chatRoomID !== 'string') {
return console.error("Invalid chat room ID!");
}
2024-06-10 22:20:36 -04:00
this.joinedRooms.add(chatRoomID); // Add the room to the list of joined rooms
this.currentTopic = chatRoomID; // Store the current topic
2024-06-10 19:49:46 -04:00
this.discovery = this.swarm.join(Buffer.from(chatRoomID, 'hex'), { client: true, server: true });
2024-06-03 19:23:14 -04:00
this.discovery.flushed().then(() => {
console.log(`Bot ${this.botName} joined the chat room.`);
this.emit('onBotJoinRoom');
2024-06-03 19:23:14 -04:00
});
}
sendTextMessage(message) {
2024-06-14 15:09:54 -04:00
console.log(`Preparing to send text message: ${message}`);
this.sendMessage(TextMessage.new(this, message));
}
async sendFileMessage(filePath, fileType) {
try {
await this.drive.ready();
const fileBuffer = fs.readFileSync(filePath);
const fileName = path.basename(filePath);
await this.drive.put(`/files/${fileName}`, fileBuffer);
const fileUrl = `http://localhost:${this.servePort}/files/${fileName}`;
const fileMessage = FileMessage.new(this, fileName, fileUrl, fileType, fileBuffer); // Pass fileBuffer to the new method
this.sendMessage(fileMessage);
} catch (error) {
console.error('Error sending file message:', error);
}
}
async sendAudioMessage(filePath, audioType) {
try {
await this.drive.ready();
const audioBuffer = fs.readFileSync(filePath);
const audioName = path.basename(filePath);
await this.drive.put(`/audio/${audioName}`, audioBuffer);
const audioUrl = `http://localhost:${this.servePort}/audio/${audioName}`;
const audioMessage = AudioMessage.new(this, audioUrl, audioType, audioBuffer); // Pass audioBuffer to the new method
this.sendMessage(audioMessage);
} catch (error) {
console.error('Error sending audio message:', error);
}
}
sendMessage(message) {
2024-06-14 15:09:54 -04:00
if (!(message instanceof Message)) {
console.error(`message does not extend Message class (TextMessage, FileMessage, AudioMessage).`, message);
return;
}
2024-06-14 09:43:17 -04:00
2024-06-14 10:34:06 -04:00
console.log("Sending message:", message);
const data = message.toJsonString();
2024-06-10 19:49:46 -04:00
const peers = [...this.swarm.connections];
2024-06-14 15:09:54 -04:00
if (peers.length === 0) {
console.warn("No active peer connections found.");
return;
}
console.log(`Sending message to ${peers.length} peers.`);
for (const peer of peers) {
try {
peer.write(data);
console.log(`Message sent to peer: ${peer.remoteAddress}`);
} catch (error) {
console.error(`Failed to send message to peer: ${peer.remoteAddress}`, error);
}
}
2024-06-03 19:23:14 -04:00
}
2024-06-14 04:43:27 -04:00
async destroy() {
await this.swarm.destroy();
2024-06-03 19:23:14 -04:00
console.log(`Bot ${this.botName} disconnected.`);
}
}
export default Client;