- Add peer-to-peer polling with real-time synchronization via PeerJS - Implement vote changing, unvoting, and proper vote validation - Add modal interface for adding poll options - Fix vote inheritance issues for new users joining rooms - Create sidebar with available polls and connected peers - Add vote count synchronization across all connected peers - Implement proper vote state validation per user - Add localStorage persistence for polls and vote states - Create responsive design with modern UI components - Replace emojis with proper text-based icons - Add comprehensive error handling and user feedback - Support multiple polls with switching capabilities - Implement conflict resolution for concurrent voting
211 lines
7.1 KiB
JavaScript
211 lines
7.1 KiB
JavaScript
// Main application entry point
|
|
class P2PPollApp {
|
|
constructor() {
|
|
this.peerManager = null;
|
|
this.pollManager = null;
|
|
this.uiController = null;
|
|
this.isInitialized = false;
|
|
}
|
|
|
|
async initialize() {
|
|
try {
|
|
// Initialize peer manager
|
|
this.peerManager = new PeerManager();
|
|
this.pollManager = new PollManager(this.peerManager);
|
|
this.uiController = new UIController(this.peerManager, this.pollManager);
|
|
|
|
// Set up event handlers
|
|
this.setupEventHandlers();
|
|
|
|
// Initialize peer connection
|
|
await this.peerManager.initialize();
|
|
|
|
// Load saved data
|
|
this.pollManager.loadFromLocalStorage();
|
|
|
|
// If there are saved polls, update the sidebar
|
|
const savedPolls = this.pollManager.getAvailablePolls();
|
|
if (savedPolls.length > 0) {
|
|
this.uiController.updatePollsList(savedPolls);
|
|
}
|
|
|
|
|
|
this.isInitialized = true;
|
|
console.log('P2P Poll App initialized successfully');
|
|
|
|
} catch (error) {
|
|
console.error('Failed to initialize app:', error);
|
|
this.uiController?.showNotification('Failed to initialize app: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
setupEventHandlers() {
|
|
// Peer manager events
|
|
this.peerManager.onConnectionStatusChange = (status, peerId) => {
|
|
this.uiController.updatePeerId(peerId);
|
|
|
|
if (status === 'connected') {
|
|
this.uiController.showNotification('Connected to P2P network', 'success');
|
|
} else if (status === 'disconnected') {
|
|
this.uiController.showNotification('Disconnected from P2P network', 'error');
|
|
}
|
|
};
|
|
|
|
this.peerManager.onPeerConnected = (peerId) => {
|
|
this.uiController.showNotification(`Peer ${peerId} connected`, 'success');
|
|
|
|
// If we're the host and have polls, send them to the new peer
|
|
if (this.peerManager.isHost) {
|
|
const availablePolls = this.pollManager.getAvailablePolls();
|
|
if (availablePolls.length > 0) {
|
|
availablePolls.forEach(poll => {
|
|
this.peerManager.sendMessage(peerId, {
|
|
type: 'poll_update',
|
|
poll: poll,
|
|
senderId: this.peerManager.getPeerId()
|
|
});
|
|
});
|
|
|
|
// Also send current poll if there is one
|
|
const currentPoll = this.pollManager.getCurrentPoll();
|
|
if (currentPoll) {
|
|
this.peerManager.sendMessage(peerId, {
|
|
type: 'current_poll',
|
|
poll: currentPoll,
|
|
senderId: this.peerManager.getPeerId()
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
this.peerManager.sendMessage(peerId, {
|
|
type: 'sync_request',
|
|
senderId: this.peerManager.getPeerId()
|
|
});
|
|
}
|
|
};
|
|
|
|
this.peerManager.onPeerDisconnected = (peerId) => {
|
|
this.uiController.showNotification(`Peer ${peerId} disconnected`, 'info');
|
|
};
|
|
|
|
this.peerManager.onMessageReceived = (message, senderId) => {
|
|
this.handleMessage(message, senderId);
|
|
};
|
|
|
|
// Poll manager events
|
|
this.pollManager.onPollCreated = (poll) => {
|
|
this.uiController.renderPoll(poll);
|
|
};
|
|
|
|
this.pollManager.onPollUpdated = (poll) => {
|
|
this.uiController.renderPoll(poll);
|
|
};
|
|
|
|
this.pollManager.onPollSelected = (poll) => {
|
|
if (poll) {
|
|
this.uiController.renderPoll(poll);
|
|
this.uiController.showActivePoll();
|
|
} else {
|
|
this.uiController.showPollCreation();
|
|
}
|
|
};
|
|
|
|
this.pollManager.onPollsListUpdated = (polls) => {
|
|
this.uiController.updatePollsList(polls);
|
|
};
|
|
}
|
|
|
|
handleMessage(message, senderId) {
|
|
console.log('Processing message:', message, 'from:', senderId);
|
|
|
|
switch (message.type) {
|
|
case 'poll_update':
|
|
this.pollManager.syncPoll(message.poll);
|
|
break;
|
|
|
|
case 'current_poll':
|
|
this.pollManager.syncPoll(message.poll);
|
|
if (this.pollManager.getCurrentPoll() && this.pollManager.getCurrentPoll().id === message.poll.id) {
|
|
this.uiController.renderPoll(message.poll);
|
|
this.uiController.showActivePoll();
|
|
}
|
|
break;
|
|
|
|
case 'vote':
|
|
this.pollManager.handleVoteMessage(message.optionId, message.voterId);
|
|
break;
|
|
|
|
case 'unvote':
|
|
this.pollManager.handleUnvoteMessage(message.voterId);
|
|
break;
|
|
|
|
case 'poll_reset':
|
|
this.pollManager.handlePollReset();
|
|
break;
|
|
|
|
case 'sync_request':
|
|
const currentPoll = this.pollManager.getCurrentPoll();
|
|
if (currentPoll) {
|
|
this.peerManager.sendMessage(senderId, {
|
|
type: 'current_poll',
|
|
poll: currentPoll,
|
|
senderId: this.peerManager.getPeerId()
|
|
});
|
|
}
|
|
|
|
const availablePolls = this.pollManager.getAvailablePolls();
|
|
availablePolls.forEach(poll => {
|
|
this.peerManager.sendMessage(senderId, {
|
|
type: 'poll_update',
|
|
poll: poll,
|
|
senderId: this.peerManager.getPeerId()
|
|
});
|
|
});
|
|
break;
|
|
|
|
default:
|
|
console.warn('Unknown message type:', message.type);
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
if (this.peerManager) {
|
|
this.peerManager.destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
const app = new P2PPollApp();
|
|
|
|
try {
|
|
await app.initialize();
|
|
} catch (error) {
|
|
console.error('App initialization failed:', error);
|
|
|
|
// Show error message to user
|
|
const errorDiv = document.createElement('div');
|
|
errorDiv.className = 'notification error';
|
|
errorDiv.textContent = 'Failed to initialize app. Please refresh the page.';
|
|
errorDiv.style.position = 'fixed';
|
|
errorDiv.style.top = '20px';
|
|
errorDiv.style.right = '20px';
|
|
errorDiv.style.zIndex = '1001';
|
|
document.body.appendChild(errorDiv);
|
|
}
|
|
|
|
// Cleanup on page unload
|
|
window.addEventListener('beforeunload', () => {
|
|
app.destroy();
|
|
});
|
|
});
|
|
|
|
// Handle page visibility changes
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (document.hidden) {
|
|
console.log('Page hidden - connection may become unstable');
|
|
} else {
|
|
console.log('Page visible - checking connection status');
|
|
}
|
|
});
|