Hello my dear friends. Welcome to the third part of our series, we’ll focus on building the client-side application for our decentralized chat system. While the server manages message storage and peer-to-peer communication, the client allows users to send and get messages.
Here’s what we’ll cover:
- Connecting the Client to the Server
- Sending Messages to Peers
- Fetching Chat History
- Enhancements for Real-Time Communication
- Debugging and Testing the System
Step 1: Setting Up the Client
Dependencies
Make sure you have the following installed:
npm install hyperdht @hyperswarm/rpc
Code: Client Initialization
Lets see how we can initialize the application for the client side.
const DHT = require('hyperdht');
const RPC = require('@hyperswarm/rpc');
class ChatClient {
constructor(serverPublicKey) {
this.dht = new DHT();
this.rpc = new RPC({ dht: this.dht });
this.serverPublicKey = Buffer.from(serverPublicKey, 'hex');
}
async connect() {
console.log('Client connected to the DHT network...');
}
async destroy() {
await this.rpc.destroy();
await this.dht.destroy();
console.log('Client disconnected.');
}
}
module.exports = ChatClient;
let’s dig through the code snippet above.
first, we setup DHT and RPC client. The DHT instance is used to discover the server on the decentralized network. And the RPC client enables communication with the server via serverPublicKey
.
Then, we add connection management through connect()
and destroy()
methods which manages the client’s lifecycle.
Step 2: Sending Messages
let’s implement a way to send messages. Take a look at below code snippet.
class ChatClient {
// Existing methods...
async sendMessage(from, to, message) {
const payload = { from, to, message };
try {
const response = await this.rpc.request(
this.serverPublicKey,
'sendMessage',
Buffer.from(JSON.stringify(payload))
);
console.log('Message sent successfully:', JSON.parse(response.toString()));
} catch (err) {
console.error('Error sending message:', err.message);
}
}
}
module.exports = ChatClient;
Here we set up payload initiation. The fields ‘from
‘, ‘to
‘, and ‘message
‘ are populated. They are sent as JSON to the server.
Then, the client calls the sendMessage
method on the server using this.rpc.request
. The errors during the request (e.g. server unreachable) are logged for debugging.
Step 3: Fetching Chat History
Let’s see how we can implement to get the chat messages.
class ChatClient {
// Existing methods...
async getMessages() {
try {
const response = await this.rpc.request(this.serverPublicKey, 'getMessages');
const messages = JSON.parse(response.toString());
console.log('Chat History:', messages);
return messages;
} catch (err) {
console.error('Error fetching messages:', err.message);
return [];
}
}
}
module.exports = ChatClient;
In this code, the client calls getMessages
method on the server. The server returns all stored messages, which the client logs and returns.
Step 4: Enhancements for Real-Time Communication
It may look very easy to capture all the messages back and forth. However, real-time communication requires constant updates or WebSocket-like behavior.
Here’s a simple way to poll for new messages:
class ChatClient {
// Existing methods...
async startPolling(interval = 5000) {
console.log('Starting polling for new messages...');
this.pollingInterval = setInterval(async () => {
try {
const messages = await this.getMessages();
console.log('Updated Chat History:', messages);
} catch (err) {
console.error('Error during polling:', err.message);
}
}, interval);
}
async stopPolling() {
clearInterval(this.pollingInterval);
console.log('Stopped polling.');
}
}
module.exports = ChatClient;
We learned that we improved the polling logic. It fetches chat history every interval
milliseconds. The stopPolling()
method clears the interval to stop polling.
Step 5: Debugging and Testing
For every code we write we should always make sure it works (else your QA friend will never let you down 😉 ). So, let’s write a test script to make sure this is working.
const ChatClient = require('./ChatClient');
(async () => {
const serverPublicKey = '<server_public_key>'; // Replace with actual key
const client = new ChatClient(serverPublicKey);
try {
await client.connect();
await client.sendMessage('Alice', 'Bob', 'Hello, Bob!');
const messages = await client.getMessages();
console.log('All Messages:', messages);
// Start polling for updates
await client.startPolling(5000);
// Stop polling after 20 seconds
setTimeout(async () => {
await client.stopPolling();
await client.destroy();
}, 20000);
} catch (err) {
console.error('Client error:', err.message);
}
})();
Debugging Tips
- Connection Issues:
- Ensure the server is running and announced on the same DHT network.
- Verify the public key matches the server’s key.
- Message Delivery:
- Check server logs to ensure messages are being received and stored correctly.
- Polling Delays:
- Adjust the polling interval based on the expected message frequency.
What’s Next?
Now that you have the client and server complete, you now have a functional decentralized chat system! In future iterations, you can enhance the application with:
- End-to-End Encryption: Secure messages using cryptographic keys.
- Group Chats: Allow multiple recipients for each message.
- Custom UI: Build a web or mobile interface for a user-friendly experience.
Congratulations! You have taken a small step on decentralization and have the basic understanding of Hyperswarm and Hyperbee. Decentralization really offers exciting possibilities, and building this chat app is just the beginning. Let me know if you try it out or have ideas to improve it. I’d love to hear your thoughts! 😊
#Decentralization #P2PChat #TechBlog #Hyperswarm #Hyperbee #ChatApplications
Discover more from Amal Gamage
Subscribe to get the latest posts sent to your email.