184 lines
7.9 KiB
JavaScript
184 lines
7.9 KiB
JavaScript
class RetroPulseOverlay {
|
|
constructor() {
|
|
this.ws = null;
|
|
this.username = null;
|
|
this.reconnectInterval = null;
|
|
this.reconnectDelay = 3000; // 3 seconds
|
|
this.lastUpdateTime = null;
|
|
// DOM elements
|
|
this.errorDisplay = document.getElementById('error-display');
|
|
this.errorMessage = document.getElementById('error-message');
|
|
this.waitingForActivity = document.getElementById('waiting-for-activity');
|
|
// Game elements
|
|
this.currentGame = document.getElementById('current-game');
|
|
this.gameIcon = document.getElementById('game-icon');
|
|
this.gameTitle = document.getElementById('game-title');
|
|
this.consoleName = document.getElementById('console-name');
|
|
// Progress bar elements
|
|
this.gameProgressSection = document.getElementById('game-progress-section');
|
|
this.progressText = document.getElementById('progress-text');
|
|
this.progressPercentage = document.getElementById('progress-percentage');
|
|
this.progressBar = document.getElementById('progress-bar');
|
|
// Achievements elements
|
|
this.achievementsContainer = document.getElementById('achievements-container');
|
|
this.achievementsList = document.getElementById('achievements-list');
|
|
this.init();
|
|
}
|
|
init() {
|
|
// Extract username from URL path
|
|
const pathParts = window.location.pathname.split('/');
|
|
this.username = decodeURIComponent(pathParts[pathParts.length - 1]);
|
|
if (!this.username || this.username === 'overlay') {
|
|
this.showError('No username specified in URL');
|
|
return;
|
|
}
|
|
console.log(`Initializing RetroPulse overlay for user: ${this.username}`);
|
|
this.connect();
|
|
}
|
|
connect() {
|
|
try {
|
|
// Determine WebSocket URL (ws:// for http, wss:// for https)
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
const wsUrl = `${protocol}//${window.location.host}/ws?user=${encodeURIComponent(this.username)}`;
|
|
console.log(`Connecting to WebSocket: ${wsUrl}`);
|
|
this.ws = new WebSocket(wsUrl);
|
|
this.ws.onopen = () => {
|
|
console.log('WebSocket connected');
|
|
if (this.reconnectInterval) {
|
|
clearInterval(this.reconnectInterval);
|
|
this.reconnectInterval = null;
|
|
}
|
|
};
|
|
this.ws.onmessage = (event) => {
|
|
try {
|
|
const message = JSON.parse(event.data);
|
|
this.handleMessage(message);
|
|
} catch (error) {
|
|
console.error('Error parsing WebSocket message:', error);
|
|
}
|
|
};
|
|
this.ws.onclose = (event) => {
|
|
console.log('WebSocket disconnected:', event.code, event.reason);
|
|
if (event.code !== 1000) { // Don't reconnect on clean close
|
|
this.scheduleReconnect();
|
|
}
|
|
};
|
|
this.ws.onerror = (error) => {
|
|
console.error('WebSocket error:', error);
|
|
};
|
|
} catch (error) {
|
|
console.error('Error creating WebSocket connection:', error);
|
|
this.showError(`Connection failed: ${error.message}`);
|
|
}
|
|
}
|
|
scheduleReconnect() {
|
|
if (this.reconnectInterval) return; // Reconnect already scheduled
|
|
console.log(`Scheduling reconnect in ${this.reconnectDelay / 1000} seconds...`);
|
|
this.reconnectInterval = setInterval(() => {
|
|
console.log('Reconnecting...');
|
|
this.connect();
|
|
}, this.reconnectDelay);
|
|
}
|
|
handleMessage(message) {
|
|
console.log('Received message:', message);
|
|
if (message.error) {
|
|
this.showError(message.error);
|
|
return;
|
|
}
|
|
if (message.type === 'initial' || message.type === 'update') {
|
|
this.updateOverlay(message.data);
|
|
}
|
|
}
|
|
updateOverlay(data) {
|
|
if (!data) return;
|
|
console.log('Updating overlay with data:', data);
|
|
this.lastUpdateTime = new Date();
|
|
this.errorDisplay.classList.add('hidden');
|
|
const hasGameProgress = data.gameProgress !== null;
|
|
const hasAchievements = data.recentAchievements && data.recentAchievements.length > 0;
|
|
if (hasGameProgress || hasAchievements) {
|
|
// We have data, so hide the waiting message
|
|
this.waitingForActivity.classList.add('hidden');
|
|
// Update Game Info and Progress Bar
|
|
if (hasGameProgress) {
|
|
this.currentGame.classList.remove('hidden');
|
|
this.updateGameInfo(data.gameProgress);
|
|
} else {
|
|
this.currentGame.classList.add('hidden');
|
|
}
|
|
// Update achievements list
|
|
if (hasAchievements) {
|
|
this.achievementsContainer.classList.remove('hidden');
|
|
this.updateAchievements(data.recentAchievements);
|
|
} else {
|
|
this.achievementsContainer.classList.add('hidden');
|
|
}
|
|
} else {
|
|
// No data to display, show the waiting message
|
|
this.waitingForActivity.classList.remove('hidden');
|
|
this.currentGame.classList.add('hidden');
|
|
this.achievementsContainer.classList.add('hidden');
|
|
}
|
|
}
|
|
updateGameInfo(gameData) {
|
|
this.currentGame.classList.remove('hidden');
|
|
this.gameTitle.textContent = gameData.title;
|
|
this.consoleName.textContent = gameData.consoleName || '';
|
|
if (gameData.icon) {
|
|
this.gameIcon.src = gameData.icon;
|
|
this.gameIcon.style.display = 'block';
|
|
} else {
|
|
this.gameIcon.style.display = 'none';
|
|
}
|
|
// Update progress bar
|
|
if (gameData.hasOwnProperty('completion') && gameData.totalAchievements > 0) {
|
|
this.progressText.textContent = `Completion: ${gameData.numAwarded} / ${gameData.totalAchievements}`;
|
|
this.progressPercentage.textContent = gameData.completion;
|
|
this.progressBar.style.width = gameData.completion;
|
|
this.gameProgressSection.classList.remove('hidden');
|
|
} else {
|
|
this.gameProgressSection.classList.add('hidden');
|
|
}
|
|
}
|
|
updateAchievements(achievements) {
|
|
// To avoid flickering, only update if content has changed.
|
|
// A simple check on the first achievement ID can work.
|
|
const firstExisting = this.achievementsList.querySelector('.achievement-item');
|
|
if (firstExisting && firstExisting.dataset.id == achievements[0].id) {
|
|
console.log('Achievements are already up-to-date.');
|
|
return;
|
|
}
|
|
console.log('Updating achievements list.');
|
|
this.achievementsList.innerHTML = ''; // Clear existing
|
|
achievements.forEach(ach => {
|
|
const item = document.createElement('li');
|
|
item.className = 'achievement-item';
|
|
item.dataset.id = ach.id; // For update checking
|
|
item.innerHTML = `
|
|
<div class="achievement-badge">
|
|
<img src="${ach.badgeName || ach.gameIcon || ''}" alt="Achievement Badge" style="width: 100%; height: 100%; object-fit: cover;">
|
|
</div>
|
|
<div class="achievement-details">
|
|
<div class="achievement-title">${ach.title}</div>
|
|
<div class="achievement-description">${ach.description}</div>
|
|
<div class="achievement-meta">
|
|
<span class="achievement-points">${ach.points}pts</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
this.achievementsList.appendChild(item);
|
|
});
|
|
}
|
|
showError(message) {
|
|
this.errorMessage.textContent = message;
|
|
this.errorDisplay.classList.remove('hidden');
|
|
this.waitingForActivity.classList.add('hidden');
|
|
this.currentGame.classList.add('hidden');
|
|
this.achievementsContainer.classList.add('hidden');
|
|
}
|
|
}
|
|
// Initialize the overlay when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
new RetroPulseOverlay();
|
|
});
|