externalise deeplinks
This commit is contained in:
199
apps.js → app.js
199
apps.js → app.js
@@ -1,5 +1,6 @@
|
||||
// Import the audio manager
|
||||
import audioManager from './audio.js';
|
||||
import deepLinkManager from './deeplinks.js';
|
||||
|
||||
// Initialize variables
|
||||
let players = [];
|
||||
@@ -545,7 +546,7 @@ function stopCameraStream() {
|
||||
}
|
||||
}
|
||||
|
||||
// Player form submit - FIXED VERSION
|
||||
// Player form submit
|
||||
playerForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -677,38 +678,15 @@ deletePlayerButton.addEventListener('click', () => {
|
||||
audioManager.play('modalClose');
|
||||
});
|
||||
|
||||
// Function to get action from URL parameters (search or hash)
|
||||
function 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) {
|
||||
console.log('Found action in search params:', searchAction);
|
||||
return searchAction;
|
||||
// Function to create deep links
|
||||
function createDeepLink(action) {
|
||||
return deepLinkManager.generateDeepLink(action);
|
||||
}
|
||||
|
||||
// Then check hash params (existing deep link mechanism)
|
||||
const hashAction = hashParams.get('action');
|
||||
if (hashAction) {
|
||||
console.log('Found action in hash params:', hashAction);
|
||||
return hashAction;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Function to handle deep link actions
|
||||
function handleActionFromUrl(action) {
|
||||
if (!action) return;
|
||||
|
||||
console.log('Processing action:', action);
|
||||
|
||||
// Execute action based on the parameter
|
||||
switch (action) {
|
||||
case 'start':
|
||||
// Function to setup deep links
|
||||
function setupDeepLinks() {
|
||||
// Register handlers for each action
|
||||
deepLinkManager.registerHandler('start', () => {
|
||||
if (gameState === 'setup' || gameState === 'paused') {
|
||||
if (players.length < 2) {
|
||||
console.log('Cannot start: Need at least 2 players');
|
||||
@@ -721,8 +699,9 @@ function handleActionFromUrl(action) {
|
||||
renderPlayers();
|
||||
saveData();
|
||||
}
|
||||
break;
|
||||
case 'pause':
|
||||
});
|
||||
|
||||
deepLinkManager.registerHandler('pause', () => {
|
||||
if (gameState === 'running') {
|
||||
gameState = 'paused';
|
||||
audioManager.play('gamePause');
|
||||
@@ -731,12 +710,14 @@ function handleActionFromUrl(action) {
|
||||
renderPlayers();
|
||||
saveData();
|
||||
}
|
||||
break;
|
||||
case 'toggle':
|
||||
// Toggle between start/pause depending on current state
|
||||
});
|
||||
|
||||
deepLinkManager.registerHandler('toggle', () => {
|
||||
// Simply trigger the game button click
|
||||
gameButton.click();
|
||||
break;
|
||||
case 'nextplayer':
|
||||
});
|
||||
|
||||
deepLinkManager.registerHandler('nextplayer', () => {
|
||||
if (gameState === 'running') {
|
||||
const nextIndex = findNextPlayerWithTimeCircular(currentPlayerIndex, 1);
|
||||
if (nextIndex !== -1 && nextIndex !== currentPlayerIndex) {
|
||||
@@ -746,85 +727,17 @@ function handleActionFromUrl(action) {
|
||||
saveData();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown action:', action);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle deep links from URL or Flic button
|
||||
function handleDeepLink() {
|
||||
// Get action from URL parameters
|
||||
const action = getActionFromUrl();
|
||||
|
||||
// Process the action if found
|
||||
if (action) {
|
||||
handleActionFromUrl(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for service worker messages (for receiving actions while app is running)
|
||||
navigator.serviceWorker.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'ACTION') {
|
||||
console.log('Received action from service worker:', event.data.action);
|
||||
handleActionFromUrl(event.data.action);
|
||||
}
|
||||
});
|
||||
|
||||
// Service Worker Registration
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
.then(registration => {
|
||||
console.log('ServiceWorker registered: ', registration);
|
||||
|
||||
// Check for and handle deep links after service worker is ready
|
||||
handleDeepLink();
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('ServiceWorker registration failed: ', error);
|
||||
|
||||
// Still try to handle deep links even if service worker failed
|
||||
handleDeepLink();
|
||||
deepLinkManager.registerHandler('reset', () => {
|
||||
// Show the reset confirmation dialog
|
||||
resetButton.click();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// If service workers aren't supported, still handle deep links
|
||||
window.addEventListener('load', handleDeepLink);
|
||||
|
||||
// Process deep links on page load
|
||||
deepLinkManager.processDeepLink();
|
||||
}
|
||||
|
||||
// Also check for hash changes (needed for handling link activation when app is already open)
|
||||
window.addEventListener('hashchange', () => {
|
||||
console.log('Hash changed, checking for actions');
|
||||
handleDeepLink();
|
||||
});
|
||||
|
||||
// Also check for navigation events that might include search parameters
|
||||
window.addEventListener('popstate', () => {
|
||||
console.log('Navigation occurred, checking for actions');
|
||||
handleDeepLink();
|
||||
});
|
||||
|
||||
// Make sure to handle rotation by adding window event listener for orientation changes
|
||||
window.addEventListener('orientationchange', () => {
|
||||
// If camera is active, adjust video dimensions
|
||||
if (cameraContainer.classList.contains('active') && stream) {
|
||||
// Give a moment for the orientation to complete
|
||||
setTimeout(() => {
|
||||
// This may cause the video to briefly reset but will ensure proper dimensions
|
||||
cameraView.srcObject = null;
|
||||
cameraView.srcObject = stream;
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up when the modal is closed
|
||||
function cleanupCameraData() {
|
||||
// Clear any captured image data
|
||||
@@ -839,12 +752,60 @@ function cleanupCameraData() {
|
||||
cameraContainer.classList.remove('active');
|
||||
}
|
||||
|
||||
// Make sure to handle rotation by adding window event listener for orientation changes
|
||||
window.addEventListener('orientationchange', () => {
|
||||
// If camera is active, adjust video dimensions
|
||||
if (cameraContainer.classList.contains('active') && stream) {
|
||||
// Give a moment for the orientation to complete
|
||||
setTimeout(() => {
|
||||
// This may cause the video to briefly reset but will ensure proper dimensions
|
||||
cameraView.srcObject = null;
|
||||
cameraView.srcObject = stream;
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for service worker messages (for receiving actions while app is running)
|
||||
navigator.serviceWorker.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'ACTION') {
|
||||
console.log('Received action from service worker:', event.data.action);
|
||||
deepLinkManager.handleAction(event.data.action);
|
||||
}
|
||||
});
|
||||
|
||||
// Service Worker Registration
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
.then(registration => {
|
||||
console.log('ServiceWorker registered: ', registration);
|
||||
|
||||
// Setup and handle deep links after service worker is ready
|
||||
setupDeepLinks();
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('ServiceWorker registration failed: ', error);
|
||||
|
||||
// Still try to handle deep links even if service worker failed
|
||||
setupDeepLinks();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// If service workers aren't supported, still handle deep links
|
||||
window.addEventListener('load', setupDeepLinks);
|
||||
}
|
||||
|
||||
// Also check for hash changes (needed for handling link activation when app is already open)
|
||||
window.addEventListener('hashchange', () => {
|
||||
console.log('Hash changed, checking for actions');
|
||||
deepLinkManager.processDeepLink();
|
||||
});
|
||||
|
||||
// Also check for navigation events that might include search parameters
|
||||
window.addEventListener('popstate', () => {
|
||||
console.log('Navigation occurred, checking for actions');
|
||||
deepLinkManager.processDeepLink();
|
||||
});
|
||||
|
||||
// Initialize the app
|
||||
loadData();
|
||||
|
||||
// Process URL parameters on initial load (needed for direct curl requests)
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', handleDeepLink);
|
||||
} else {
|
||||
handleDeepLink();
|
||||
}
|
||||
135
deeplinks.js
Normal file
135
deeplinks.js
Normal file
@@ -0,0 +1,135 @@
|
||||
// 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;
|
||||
@@ -156,8 +156,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Main application script -->
|
||||
<script type="module" src="audio.js"></script>
|
||||
<script type="module" src="apps.js"></script>
|
||||
<script type="module" src="app.js"></script>
|
||||
<script>
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register("/sw.js")
|
||||
|
||||
@@ -74,12 +74,26 @@
|
||||
"url": "/?action=pause",
|
||||
"icons": [{ "src": "/icons/pause.png", "sizes": "192x192" }]
|
||||
},
|
||||
{
|
||||
"name": "Toggle Game",
|
||||
"short_name": "Toggle",
|
||||
"description": "Toggle game state (start/pause)",
|
||||
"url": "/?action=toggle",
|
||||
"icons": [{ "src": "/icons/toggle.png", "sizes": "192x192" }]
|
||||
},
|
||||
{
|
||||
"name": "Next Player",
|
||||
"short_name": "Next",
|
||||
"description": "Go to next player",
|
||||
"url": "/?action=nextplayer",
|
||||
"icons": [{ "src": "/icons/next.png", "sizes": "192x192" }]
|
||||
},
|
||||
{
|
||||
"name": "Reset Game",
|
||||
"short_name": "Reset",
|
||||
"description": "Reset the entire game",
|
||||
"url": "/?action=reset",
|
||||
"icons": [{ "src": "/icons/reset.png", "sizes": "192x192" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
21
sw.js
21
sw.js
@@ -8,6 +8,7 @@ const CACHE_FILES = [
|
||||
'/index.html',
|
||||
'/app.js',
|
||||
'/audio.js',
|
||||
'/deeplinks.js',
|
||||
'/styles.css',
|
||||
'/manifest.json',
|
||||
'/icons/android-chrome-192x192.png',
|
||||
@@ -17,6 +18,9 @@ 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');
|
||||
@@ -64,9 +68,20 @@ self.addEventListener('fetch', event => {
|
||||
// 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)
|
||||
@@ -115,6 +130,12 @@ self.addEventListener('message', event => {
|
||||
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 => {
|
||||
|
||||
Reference in New Issue
Block a user