diff --git a/frontend/public/sw.js b/frontend/public/sw.js index c8d352d..5a3179e 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'agency-cache-v2'; +const CACHE_NAME = 'agency-cache-v3'; const ASSETS = [ '/', '/index.html', @@ -34,8 +34,16 @@ self.addEventListener('fetch', (event) => { }) ); }); + self.addEventListener('push', (event) => { - const data = event.data ? event.data.json() : { title: 'Notification', body: 'New update from Agency' }; + let data = { title: 'Notification', body: 'New update from Agency' }; + try { + if (event.data) { + data = event.data.json(); + } + } catch (e) { + console.error('Error parsing push data:', e); + } const options = { body: data.body, @@ -44,7 +52,9 @@ self.addEventListener('push', (event) => { vibrate: [100, 50, 100], data: { dateOfArrival: Date.now(), - primaryKey: '1' + primaryKey: '1', + taskId: data.task_id, + runId: data.run_id } }; @@ -55,7 +65,27 @@ self.addEventListener('push', (event) => { self.addEventListener('notificationclick', (event) => { event.notification.close(); + + const taskId = event.notification.data.taskId; + const runId = event.notification.data.runId; + + let url = '/'; + if (taskId && runId) { + url = `/?taskId=${taskId}&runId=${runId}`; + } + event.waitUntil( - clients.openWindow('/') + clients.matchAll({ type: 'window', includeUncontrolled: true }).then((windowClients) => { + // Check if there is already a window open and focus it, or open a new one + for (let client of windowClients) { + if ('focus' in client) { + // Navigate the existing client to the new URL if it's the same app + return client.navigate(url).then(c => c.focus()); + } + } + if (clients.openWindow) { + return clients.openWindow(url); + } + }) ); }); diff --git a/frontend/src/main.js b/frontend/src/main.js index 2a03a04..ffae909 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -159,6 +159,16 @@ async function fetchTasks() { const response = await fetchWithAuth(`${API_URL}/tasks`); const newTasks = await response.json(); + // Check for deep link in URL + const params = new URLSearchParams(window.location.search); + const urlTaskId = params.get('taskId'); + const urlRunId = params.get('runId'); + + if (urlTaskId && !state.selectedTaskId) { + state.selectedTaskId = urlTaskId; + state.selectedRunId = urlRunId; + } + // Check if we should follow the latest run let newSelectedRunId = state.selectedRunId; if (state.selectedTaskId) { @@ -179,6 +189,12 @@ async function fetchTasks() { tasks: newTasks, selectedRunId: newSelectedRunId }); + + // If we just loaded from a deep link, clear the params and select it + if (urlTaskId) { + window.history.replaceState({}, document.title, "/"); + selectTask(urlTaskId, urlRunId); + } } catch (error) { console.error('Error fetching tasks:', error); } @@ -806,6 +822,16 @@ async function initializeApp() { if (hasSession) { appEl.classList.remove('hidden'); loginOverlay.classList.add('hidden'); + + // Handle deep links from notifications + const params = new URLSearchParams(window.location.search); + const taskId = params.get('taskId'); + const runId = params.get('runId'); + if (taskId) { + state.selectedTaskId = taskId; + state.selectedRunId = runId; + } + await fetchTasks(); connectWebSocket(); } else { diff --git a/src/domain/notifications/push.rs b/src/domain/notifications/push.rs index 505c38f..478fe2a 100644 --- a/src/domain/notifications/push.rs +++ b/src/domain/notifications/push.rs @@ -1,5 +1,6 @@ use crate::error::AppResult; use serde::{Deserialize, Serialize}; +use uuid::Uuid; use web_push::*; pub struct PushSender { @@ -56,6 +57,8 @@ impl PushSender { subscription: &PushSubscription, title: &str, body: &str, + task_id: Option, + run_id: Option, ) -> AppResult<()> { let subscription_info = SubscriptionInfo::new( subscription.endpoint.clone(), @@ -81,6 +84,8 @@ impl PushSender { let payload = serde_json::to_vec(&serde_json::json!({ "title": title, "body": body, + "task_id": task_id, + "run_id": run_id, })) .map_err(|e| crate::error::AppError::Internal(e.to_string()))?; diff --git a/src/domain/tasks.rs b/src/domain/tasks.rs index ba69ae7..90c2286 100644 --- a/src/domain/tasks.rs +++ b/src/domain/tasks.rs @@ -171,7 +171,13 @@ pub async fn execute_agent_run( tokio::spawn(async move { if let Err(e) = sender - .send_notification(&sub_data, &format!("Task Completed: {}", status), &goal) + .send_notification( + &sub_data, + &format!("Task Completed: {}", status), + &goal, + Some(task_id), + Some(run_id), + ) .await { tracing::error!("Failed to send notification in background task: {}", e); diff --git a/src/scheduler.rs b/src/scheduler.rs index 3b5b0a8..feac531 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -201,6 +201,8 @@ impl Scheduler { &sub_data, &format!("Scheduled Task Completed: {}", status), &goal, + Some(task_id), + Some(run_id), ) .await; });