This commit is contained in:
cpu
2025-03-28 05:50:28 +01:00
parent d1ad962ec3
commit 2e47461f34
5 changed files with 15 additions and 306 deletions

36
app.js
View File

@@ -5,7 +5,6 @@ import * as ui from './ui.js';
import * as timer from './timer.js';
import camera from './camera.js'; // Default export
import audioManager from './audio.js';
import deepLinkManager from './deeplinks.js';
import * as pushFlic from './pushFlicIntegration.js';
// --- Core Game Actions ---
@@ -338,15 +337,9 @@ function setupServiceWorker() {
// Initialize Flic integration (which will try to subscribe)
initFlic();
// Setup deep links *after* SW might be ready
setupDeepLinks();
})
.catch(error => {
console.error('ServiceWorker registration failed:', error);
// Still setup deep links even if SW fails
setupDeepLinks();
// Maybe inform user push notifications won't work
});
});
// Listen for SW controller changes
@@ -357,40 +350,15 @@ function setupServiceWorker() {
} else {
console.warn('ServiceWorker not supported.');
// Setup deep links anyway
window.addEventListener('load', setupDeepLinks);
}
}
function handleServiceWorkerMessage(event) {
console.log('[App] Message received from Service Worker:', event.data);
console.log('[App] Message received from Service Worker:', event.data);
if (event.data?.type === 'flic-action') {
const { action, button, timestamp } = event.data;
pushFlic.handleFlicAction(action, button, timestamp);
}
else if (event.data?.type === 'ACTION') { // Handle deep link actions sent from SW
console.log('Received action from service worker via postMessage:', event.data.action);
deepLinkManager.handleAction(event.data.action);
}
// Add other message type handlers if needed
}
// --- Deep Link Setup ---
function setupDeepLinks() {
deepLinkManager.registerHandler('start', startGame);
deepLinkManager.registerHandler('pause', pauseGame);
deepLinkManager.registerHandler('toggle', togglePauseResume);
deepLinkManager.registerHandler('nextplayer', nextPlayer);
deepLinkManager.registerHandler('prevplayer', previousPlayer); // Add previous player handler
deepLinkManager.registerHandler('reset', handleResetButtonClick); // Show confirmation
// Process initial deep link on load
deepLinkManager.processDeepLink();
// Listen for subsequent deep links
window.addEventListener('hashchange', deepLinkManager.processDeepLink);
// Also listen for popstate if using history API or query params
window.addEventListener('popstate', deepLinkManager.processDeepLink);
}
// --- Flic Integration Setup ---
@@ -453,7 +421,7 @@ function initialize() {
ui.elements.resetCancelButton.addEventListener('click', handleResetCancel);
ui.elements.cameraButton.addEventListener('click', handleCameraButtonClick);
// 6. Setup Service Worker (which also initializes Flic and Deep Links)
// 6. Setup Service Worker (which also initializes Flic)
setupServiceWorker();
// 7. Initial UI Update based on loaded state

View File

@@ -1,135 +0,0 @@
// deeplinks.js - Deep Link Manager for Game Timer
// Available actions
const VALID_ACTIONS = ['start', 'pause', 'toggle', 'nextplayer', 'reset'];
// Class to manage all deep link functionality
class DeepLinkManager {
constructor() {
this.actionHandlers = {};
// Initialize listeners
this.initServiceWorkerListener();
this.initHashChangeListener();
this.initPopStateListener();
}
// Register action handlers
registerHandler(action, handlerFn) {
if (VALID_ACTIONS.includes(action)) {
this.actionHandlers[action] = handlerFn;
console.log(`Registered handler for action: ${action}`);
} else {
console.warn(`Attempted to register handler for invalid action: ${action}`);
}
}
// Extract action from URL parameters (search or hash)
getActionFromUrl() {
// Check for action in both search params and hash
const searchParams = new URLSearchParams(window.location.search);
const hashParams = new URLSearchParams(window.location.hash.substring(1));
// First check search params (for direct curl or navigation)
const searchAction = searchParams.get('action');
if (searchAction && VALID_ACTIONS.includes(searchAction)) {
console.log('Found action in search params:', searchAction);
return searchAction;
}
// Then check hash params (existing deep link mechanism)
const hashAction = hashParams.get('action');
if (hashAction && VALID_ACTIONS.includes(hashAction)) {
console.log('Found action in hash params:', hashAction);
return hashAction;
}
return null;
}
// Process an action
handleAction(action) {
if (!action) return;
console.log('Processing action:', action);
if (this.actionHandlers[action]) {
this.actionHandlers[action]();
} else {
console.log('No handler registered for action:', action);
}
}
// Handle deep links from URL
processDeepLink() {
// Get action from URL parameters
const action = this.getActionFromUrl();
// Process the action if found
if (action) {
this.handleAction(action);
// Clear the parameters to prevent duplicate actions if page is refreshed
if (window.history && window.history.replaceState) {
// Create new URL without the action parameter
const newUrl = window.location.pathname;
window.history.replaceState({}, document.title, newUrl);
}
}
}
// Initialize service worker message listener
initServiceWorkerListener() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('message', (event) => {
if (event.data && event.data.type === 'ACTION') {
console.log('Received action from service worker:', event.data.action);
this.handleAction(event.data.action);
}
});
}
}
// Initialize hash change listener
initHashChangeListener() {
window.addEventListener('hashchange', () => {
console.log('Hash changed, checking for actions');
this.processDeepLink();
});
}
// Initialize popstate listener for navigation events
initPopStateListener() {
window.addEventListener('popstate', () => {
console.log('Navigation occurred, checking for actions');
this.processDeepLink();
});
}
// Send an action to the service worker
sendActionToServiceWorker(action) {
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({
type: 'PROCESS_ACTION',
action: action
});
}
}
// Generate a deep link URL for a specific action
generateDeepLink(action, useHash = false) {
if (!VALID_ACTIONS.includes(action)) {
console.warn(`Cannot generate deep link for invalid action: ${action}`);
return null;
}
const baseUrl = window.location.origin + window.location.pathname;
return useHash ?
`${baseUrl}#action=${action}` :
`${baseUrl}?action=${action}`;
}
}
// Export singleton instance
const deepLinkManager = new DeepLinkManager();
export default deepLinkManager;

View File

@@ -9,14 +9,6 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<link rel="stylesheet" href="styles.css">
<!-- Deep Linking - App Links for Android -->
<link rel="alternate" href="android-app://eu.virtonline.gametimer/https://game-timer.virtonline.eu" />
<!-- Deep Linking - Universal Links for iOS -->
<meta name="apple-itunes-app" content="app-id=yourAppID, app-argument=https://game-timer.virtonline.eu">
<!-- Deep Linking - Web App URL Handling -->
<link rel="alternate" href="web+gametimer://action" />
</head>
<body>
<div class="app-container">
@@ -108,53 +100,7 @@
<button id="cameraCaptureButton" class="camera-button-large"></button>
</div>
</div>
<!-- Script for handling URL scheme and deep links -->
<script type="module">
// Register the custom URL protocol handler (web+gametimer://)
if ('registerProtocolHandler' in navigator) {
try {
navigator.registerProtocolHandler(
'web+gametimer',
'https://game-timer.virtonline.eu/?action=%s',
'Game Timer'
);
console.log('Protocol handler registered');
} catch (e) {
console.error('Failed to register protocol handler:', e);
}
}
// Function to parse URL parameters
function getUrlParams() {
const searchParams = new URLSearchParams(window.location.search);
const hashParams = new URLSearchParams(window.location.hash.substring(1));
// Check search parameters first (for direct links)
const action = searchParams.get('action');
if (action) {
// Clean the action parameter (remove 'web+gametimer://' if present)
return action.replace('web+gametimer://', '');
}
// Then check hash parameters (for deep links)
return hashParams.get('action');
}
// Initialize URL handling
function initUrlHandling() {
const action = getUrlParams();
if (action) {
console.log('URL action detected:', action);
// Set the action in the hash to be processed by the main app
window.location.hash = `action=${action}`;
}
}
// Run initialization when DOM is fully loaded
document.addEventListener('DOMContentLoaded', initUrlHandling);
</script>
<!-- Main application script -->
<script type="module" src="app.js"></script>
<script>

View File

@@ -101,7 +101,6 @@ async function subscribeToPush() {
return;
}
// Send to backend
await sendSubscriptionToServer(finalSubscription, buttonId);
} catch (error) {
@@ -154,7 +153,7 @@ async function sendSubscriptionToServer(subscription, buttonId) {
// Called by app.js when a message is received from the service worker
export function handleFlicAction(action, buttonId, timestamp) {
console.log(`[PushFlic] Received Action: ${action} from Button: ${buttonId}`);
console.log(`[PushFlic] Received Action: ${action} from Button: ${buttonId} at ${timestamp}`);
// Ignore actions from buttons other than the configured one
if (buttonId !== FLIC_BUTTON_ID) {
@@ -166,7 +165,17 @@ export function handleFlicAction(action, buttonId, timestamp) {
const handler = actionHandlers[action];
if (handler && typeof handler === 'function') {
console.log(`[PushFlic] Executing handler for ${action}`);
handler(); // Execute the handler registered in app.js
// Execute the handler registered in app.js
switch(action) {
case FLIC_ACTIONS.SINGLE_CLICK:
nextPlayer();
break;
case FLIC_ACTIONS.DOUBLE_CLICK:
previousPlayer();
break;
case FLIC_ACTIONS.HOLD:
togglePauseResume();
}
} else {
console.warn(`[PushFlic] No handler registered for action: ${action}`);
}

79
sw.js
View File

@@ -8,7 +8,6 @@ const CACHE_FILES = [
'/index.html',
'/app.js',
'/audio.js',
'/deeplinks.js',
'/styles.css',
'/manifest.json',
'/icons/android-chrome-192x192.png',
@@ -18,9 +17,6 @@ const CACHE_FILES = [
'/icons/favicon-16x16.png'
];
// Valid deep link actions
const VALID_ACTIONS = ['start', 'pause', 'toggle', 'nextplayer', 'reset'];
// Install event - Cache files
self.addEventListener('install', event => {
console.log('[ServiceWorker] Install');
@@ -56,57 +52,6 @@ self.addEventListener('activate', event => {
);
});
// Fetch event - Serve from cache, fallback to network
self.addEventListener('fetch', event => {
console.log('[ServiceWorker] Fetch', event.request.url);
// For navigation requests that include our deep link parameters,
// skip the cache and go straight to network
if (event.request.mode === 'navigate') {
const url = new URL(event.request.url);
// Check if request has action parameter or hash
if (url.searchParams.has('action') || url.hash.includes('action=')) {
console.log('[ServiceWorker] Processing deep link navigation');
// Verify the action is valid
const action = url.searchParams.get('action') ||
new URLSearchParams(url.hash.substring(1)).get('action');
if (action && VALID_ACTIONS.includes(action)) {
console.log('[ServiceWorker] Valid action found:', action);
// For navigation requests with valid actions, let the request go through
// so our app can handle the deep link
return;
}
}
}
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request)
.then(res => {
// Check if we should cache this response
if (shouldCacheResponse(event.request, res)) {
return caches.open(CACHE_NAME)
.then(cache => {
console.log('[ServiceWorker] Caching new resource:', event.request.url);
cache.put(event.request, res.clone());
return res;
});
}
return res;
});
})
.catch(error => {
console.log('[ServiceWorker] Fetch failed; returning offline page', error);
// You could return a custom offline page here
})
);
});
// Helper function to determine if a response should be cached
function shouldCacheResponse(request, response) {
// Only cache GET requests
@@ -124,30 +69,6 @@ function shouldCacheResponse(request, response) {
return true;
}
// Handle deep links from other apps (including Flic)
self.addEventListener('message', event => {
if (event.data && event.data.type === 'PROCESS_ACTION') {
const action = event.data.action;
console.log('[ServiceWorker] Received action message:', action);
// Validate the action
if (!VALID_ACTIONS.includes(action)) {
console.warn('[ServiceWorker] Invalid action received:', action);
return;
}
// Broadcast the action to all clients
self.clients.matchAll().then(clients => {
clients.forEach(client => {
client.postMessage({
type: 'ACTION',
action: action
});
});
});
}
});
self.addEventListener('push', event => {
console.log('[ServiceWorker] Push received');