refactor
This commit is contained in:
@@ -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);
|
||||
}
|
||||
})();
|
||||
@@ -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
|
||||
|
||||
@@ -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
88
src/js/env-loader.js
Normal 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
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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.');
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user