This commit is contained in:
cpu
2025-03-29 03:45:26 +01:00
parent 5763407edc
commit 832f19235f
31 changed files with 373 additions and 139 deletions

View File

@@ -6,6 +6,7 @@ import * as timer from './core/timer.js';
import camera from './ui/camera.js'; // Default export
import audioManager from './ui/audio.js';
import * as pushFlic from './services/pushFlicIntegration.js';
import { initEnv } from './env-loader.js';
// Import externalized modules
import * as gameActions from './core/gameActions.js';
@@ -15,9 +16,17 @@ import * as serviceWorkerManager from './services/serviceWorkerManager.js';
// --- Initialization ---
function initialize() {
async function initialize() {
console.log("Initializing Game Timer App...");
// 0. Wait for environment variables to load
try {
await initEnv();
console.log("Environment variables loaded");
} catch (error) {
console.warn("Failed to load environment variables, using defaults:", error);
}
// 1. Load saved state or defaults
state.loadData();
@@ -88,4 +97,11 @@ function initialize() {
}
// --- Start the application ---
initialize();
// We need to use an async IIFE to await the async initialize function
(async () => {
try {
await initialize();
} catch (error) {
console.error("Error initializing application:", error);
}
})();

View File

@@ -1,6 +1,17 @@
// config.js
export const PUBLIC_VAPID_KEY = 'BKfRJXjSQmAJ452gLwlK_8scGrW6qMU1mBRp39ONtcQHkSsQgmLAaODIyGbgHyRpnDEv3HfXV1oGh3SC0fHxY0E';
export const BACKEND_URL = 'https://webpush.virtonline.eu';
import { getEnv } from './env-loader.js';
export function getPublicVapidKey() {
// Get the VAPID key from environment variables through the env-loader
return getEnv('PUBLIC_VAPID_KEY', 'BKfRJXjSQmAJ452gLwlK_8scGrW6qMU1mBRp39ONtcQHkSsQgmLAaODIyGbgHyRpnDEv3HfXV1oGh3SC0fHxY0E');
}
// The VAPID key should not be exposed directly in the source code
// Use the getter function instead: getPublicVapidKey()
// export const PUBLIC_VAPID_KEY = 'BKfRJXjSQmAJ452gLwlK_8scGrW6qMU1mBRp39ONtcQHkSsQgmLAaODIyGbgHyRpnDEv3HfXV1oGh3SC0fHxY0E';
// Get backend URL from environment variables
export const BACKEND_URL = getEnv('BACKEND_URL', 'https://webpush.virtonline.eu');
export const FLIC_BUTTON_ID = 'game-button'; // Example ID, might need configuration
export const LOCAL_STORAGE_KEY = 'gameTimerData';
export const FLIC_BATTERY_THRESHOLD = 50; // Battery percentage threshold for low battery warning

View File

@@ -7,18 +7,23 @@ let timerInterval = null;
let onTimerTickCallback = null; // Callback for UI updates
let onPlayerSwitchCallback = null; // Callback for when player switches due to time running out
let onGameOverCallback = null; // Callback for when all players run out of time
let timeExpiredFlagsById = new Map(); // Track which players have had their timeout sound played
export function initTimer(options) {
onTimerTickCallback = options.onTimerTick;
onPlayerSwitchCallback = options.onPlayerSwitch;
onGameOverCallback = options.onGameOver;
timeExpiredFlagsById.clear(); // Reset flags on init
}
export function startTimer() {
if (timerInterval) clearInterval(timerInterval); // Clear existing interval if any
// Stop any previous sounds (like low time warning) before starting fresh
audioManager.stopAllSounds(); // Consider if this is too aggressive
audioManager.stopAllSounds();
// Reset the expired sound flags when starting a new timer
timeExpiredFlagsById.clear();
timerInterval = setInterval(() => {
const currentPlayerIndex = state.getCurrentPlayerIndex();
@@ -35,31 +40,23 @@ export function startTimer() {
const newTime = currentPlayer.remainingTime - 1;
state.updatePlayerTime(currentPlayerIndex, newTime); // Update state
// Play timer sounds
// Play timer sounds - ensure we're not leaking audio resources
audioManager.playTimerSound(newTime);
// Notify UI to update
if (onTimerTickCallback) onTimerTickCallback();
} else { // Current player's time just hit 0 or was already 0
// Ensure time is exactly 0 if it somehow went negative (unlikely with check above)
if(currentPlayer.remainingTime < 0) {
// Ensure time is exactly 0 if it somehow went negative
if(currentPlayer.remainingTime < 0) {
state.updatePlayerTime(currentPlayerIndex, 0);
}
// Stop this player's timer tick sound if it was playing
// audioManager.stop('timerTick'); // Or specific low time sound
// Play time expired sound (only once)
// Check if we just hit zero to avoid playing repeatedly
// This logic might be complex, audioManager could handle idempotency
if (currentPlayer.remainingTime === 0 && !currentPlayer.timeExpiredSoundPlayed) {
audioManager.playTimerExpired();
// We need a way to mark that the sound played for this player this turn.
// This might require adding a temporary flag to the player state,
// or handling it within the audioManager. Let's assume audioManager handles it for now.
}
// Play time expired sound (only once per player per game)
if (!timeExpiredFlagsById.has(currentPlayer.id)) {
audioManager.playTimerExpired();
timeExpiredFlagsById.set(currentPlayer.id, true);
}
// Check if the game should end or switch player
if (state.areAllTimersFinished()) {
@@ -69,10 +66,11 @@ export function startTimer() {
// Find the *next* player who still has time
const nextPlayerIndex = state.findNextPlayerWithTime(); // This finds ANY player with time
if (nextPlayerIndex !== -1 && nextPlayerIndex !== currentPlayerIndex) {
// Switch player
// Switch player and ensure we stop any sounds from current player
audioManager.stopTimerSounds(); // Stop specific timer sounds before switching
if (onPlayerSwitchCallback) onPlayerSwitchCallback(nextPlayerIndex);
// Play switch sound (might be handled in app.js based on state change)
// audioManager.play('playerSwitch'); // Or let app.js handle sounds based on actions
// Immediately update UI after switch
if (onTimerTickCallback) onTimerTickCallback();
} else if (nextPlayerIndex === -1) {
@@ -81,7 +79,7 @@ export function startTimer() {
stopTimer(); // Stop timer if state is inconsistent
if (onGameOverCallback) onGameOverCallback(); // Treat as game over
}
// If nextPlayerIndex is the same as currentPlayerIndex, means others are out of time, let this timer continue (or rather, stop ticking down as remainingTime is 0)
// If nextPlayerIndex is the same as currentPlayerIndex, means others are out of time, let this timer continue
}
}
}, 1000);
@@ -90,10 +88,17 @@ export function startTimer() {
export function stopTimer() {
clearInterval(timerInterval);
timerInterval = null;
// Optionally stop timer sounds here if needed
// audioManager.stop('timerTick');
// Stop all timer-related sounds to prevent them from continuing to play
audioManager.stopTimerSounds();
}
export function isTimerRunning() {
return timerInterval !== null;
}
// Clean up resources when the application is closing or component unmounts
export function cleanup() {
stopTimer();
timeExpiredFlagsById.clear();
audioManager.stopAllSounds();
}

88
src/js/env-loader.js Normal file
View File

@@ -0,0 +1,88 @@
// env-loader.js
// This module is responsible for loading environment variables from .env file
// Store environment variables in a global object
window.ENV_CONFIG = {};
// Function to load environment variables from .env file
async function loadEnvVariables() {
try {
// Fetch the .env file as text
const response = await fetch('/.env');
if (!response.ok) {
console.warn('Could not load .env file. Using default values.');
setDefaultEnvValues();
return;
}
const envText = await response.text();
// Parse the .env file content
const envVars = parseEnvFile(envText);
// Store in the global ENV_CONFIG object
window.ENV_CONFIG = { ...window.ENV_CONFIG, ...envVars };
console.log('Environment variables loaded successfully');
} catch (error) {
console.error('Error loading environment variables:', error);
setDefaultEnvValues();
}
}
// Parse .env file content into key-value pairs
function parseEnvFile(envText) {
const envVars = {};
// Split by lines and process each line
envText.split('\n').forEach(line => {
// Skip empty lines and comments
if (!line || line.trim().startsWith('#')) return;
// Extract key-value pairs
const match = line.match(/^\s*([\w.-]+)\s*=\s*(.*)?\s*$/);
if (match) {
const key = match[1];
let value = match[2] || '';
// Remove quotes if present
if (value.startsWith('"') && value.endsWith('"')) {
value = value.slice(1, -1);
}
envVars[key] = value;
}
});
return envVars;
}
// Set default values for required environment variables
function setDefaultEnvValues() {
window.ENV_CONFIG = {
...window.ENV_CONFIG,
PUBLIC_VAPID_KEY: 'your_public_vapid_key_here',
BACKEND_URL: 'https://your-push-server.example.com'
};
console.log('Using default environment values');
}
// Export function to initialize environment variables
export async function initEnv() {
await loadEnvVariables();
return window.ENV_CONFIG;
}
// Auto-initialize when imported
initEnv();
// Export access functions for environment variables
export function getEnv(key, defaultValue = '') {
return window.ENV_CONFIG[key] || defaultValue;
}
export default {
initEnv,
getEnv
};

View File

@@ -1,5 +1,5 @@
// pushFlicIntegration.js
import { PUBLIC_VAPID_KEY, BACKEND_URL, FLIC_BUTTON_ID, FLIC_ACTIONS, FLIC_BATTERY_THRESHOLD } from '../config.js';
import { getPublicVapidKey, BACKEND_URL, FLIC_BUTTON_ID, FLIC_ACTIONS, FLIC_BATTERY_THRESHOLD } from '../config.js';
let pushSubscription = null; // Keep track locally if needed
let actionHandlers = {}; // Store handlers for different Flic actions
@@ -139,7 +139,7 @@ async function subscribeToPush() {
if (existingSubscription) {
const existingKey = existingSubscription.options?.applicationServerKey;
if (!existingKey || arrayBufferToBase64(existingKey) !== PUBLIC_VAPID_KEY) {
if (!existingKey || arrayBufferToBase64(existingKey) !== getPublicVapidKey()) {
console.log('VAPID key mismatch or missing. Unsubscribing old subscription.');
await existingSubscription.unsubscribe();
existingSubscription = null;
@@ -153,7 +153,7 @@ async function subscribeToPush() {
let finalSubscription = existingSubscription;
if (needsResubscribe) {
console.log('Subscribing for push notifications...');
const applicationServerKey = urlBase64ToUint8Array(PUBLIC_VAPID_KEY);
const applicationServerKey = urlBase64ToUint8Array(getPublicVapidKey());
finalSubscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey

View File

@@ -32,7 +32,7 @@ export function handleServiceWorkerMessage(event) {
export function setupServiceWorker(messageHandler) {
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/public/sw.js')
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('ServiceWorker registered successfully.');

View File

@@ -302,6 +302,14 @@ const audioManager = {
this.lastTickTime = 0;
},
// Stop timer-specific sounds (tick, low time warning)
stopTimerSounds() {
// Reset tick fade timer when stopping timer sounds
this.lastTickTime = 0;
// In this implementation, sounds are so short-lived that
// they don't need to be explicitly stopped, just fade prevention is enough
},
// Reset the tick fading (call this when timer is paused or player changes)
resetTickFade() {
this.lastTickTime = 0;