From fc81a938916a0d2a6b913e3934a820ff1ecb1391 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Thu, 28 Aug 2025 23:24:05 +0200 Subject: [PATCH] WIP --- changedetectionio/flask_app.py | 30 ++++++++ .../notification/BrowserNotifications.py | 33 ++++++++ .../static/js/browser-notifications.js | 76 ++++++++++++++++--- 3 files changed, 128 insertions(+), 11 deletions(-) diff --git a/changedetectionio/flask_app.py b/changedetectionio/flask_app.py index c7737b1a2..b6dc57e3f 100644 --- a/changedetectionio/flask_app.py +++ b/changedetectionio/flask_app.py @@ -548,6 +548,36 @@ def changedetection_app(config=None, datastore_o=None): logger.error(f"Test browser notification failed: {e}") return jsonify({'success': False, 'message': f'Error: {str(e)}'}), 500 + @app.route("/clear-all-browser-notifications", methods=['POST']) + def clear_all_browser_notifications(): + """Clear all browser notification subscriptions from the datastore""" + try: + from flask import jsonify + + # Get current subscription count + browser_subscriptions = datastore.data.get('settings', {}).get('application', {}).get('browser_subscriptions', []) + subscription_count = len(browser_subscriptions) + + # Clear all subscriptions + if 'settings' not in datastore.data: + datastore.data['settings'] = {} + if 'application' not in datastore.data['settings']: + datastore.data['settings']['application'] = {} + + datastore.data['settings']['application']['browser_subscriptions'] = [] + datastore.needs_write = True + + logger.info(f"Cleared {subscription_count} browser notification subscriptions") + + return jsonify({ + 'success': True, + 'message': f'Cleared {subscription_count} browser notification subscription(s)' + }) + + except Exception as e: + logger.error(f"Failed to clear all browser notifications: {e}") + return jsonify({'success': False, 'message': f'Clear all failed: {str(e)}'}), 500 + import changedetectionio.blueprint.browser_steps as browser_steps app.register_blueprint(browser_steps.construct_blueprint(datastore), url_prefix='/browser-steps') diff --git a/changedetectionio/notification/BrowserNotifications.py b/changedetectionio/notification/BrowserNotifications.py index 31df9ea59..fb128fb1b 100644 --- a/changedetectionio/notification/BrowserNotifications.py +++ b/changedetectionio/notification/BrowserNotifications.py @@ -152,6 +152,39 @@ class BrowserNotificationsUnsubscribe(Resource): return {'success': False, 'message': f'Unsubscribe failed: {str(e)}'}, 500 +class BrowserNotificationsClearAll(Resource): + """Clear all browser notification subscriptions""" + + @marshal_with(browser_notifications_fields) + def post(self): + try: + # Get datastore + datastore = current_app.config.get('DATASTORE') + if not datastore: + return {'success': False, 'message': 'Datastore not available'}, 500 + + # Get current subscription count + browser_subscriptions = datastore.data.get('settings', {}).get('application', {}).get('browser_subscriptions', []) + subscription_count = len(browser_subscriptions) + + # Clear all subscriptions + if 'settings' not in datastore.data: + datastore.data['settings'] = {} + if 'application' not in datastore.data['settings']: + datastore.data['settings']['application'] = {} + + datastore.data['settings']['application']['browser_subscriptions'] = [] + datastore.needs_write = True + + logger.info(f"Cleared {subscription_count} browser notification subscriptions") + + return {'success': True, 'message': f'Cleared {subscription_count} browser notification subscription(s)'} + + except Exception as e: + logger.error(f"Failed to clear all browser notifications: {e}") + return {'success': False, 'message': f'Clear all failed: {str(e)}'}, 500 + + class BrowserNotificationsTest(Resource): """Send a test browser notification""" diff --git a/changedetectionio/static/js/browser-notifications.js b/changedetectionio/static/js/browser-notifications.js index 02f9fd780..09a0dd862 100644 --- a/changedetectionio/static/js/browser-notifications.js +++ b/changedetectionio/static/js/browser-notifications.js @@ -319,6 +319,42 @@ class BrowserNotifications { return outputArray; } + async checkExistingSubscription() { + /** + * Check if we already have a valid browser subscription + * Updates this.isSubscribed based on actual browser state + */ + try { + if (!this.serviceWorkerRegistration) { + this.isSubscribed = false; + return; + } + + const existingSubscription = await this.serviceWorkerRegistration.pushManager.getSubscription(); + + if (existingSubscription) { + // We have a subscription - verify it's still valid and matches our VAPID key + const subscriptionJson = existingSubscription.toJSON(); + + // Check if the endpoint is still active (basic validation) + if (subscriptionJson.endpoint && subscriptionJson.keys) { + console.log('Found existing valid subscription'); + this.isSubscribed = true; + } else { + console.log('Found invalid subscription, clearing...'); + await existingSubscription.unsubscribe(); + this.isSubscribed = false; + } + } else { + console.log('No existing subscription found'); + this.isSubscribed = false; + } + } catch (error) { + console.warn('Failed to check existing subscription:', error); + this.isSubscribed = false; + } + } + async clearExistingSubscription() { /** * Clear any existing push subscription that might conflict with our VAPID keys @@ -366,21 +402,39 @@ Would you like to automatically clear the old subscription and retry?`; * Clear all browser notification subscriptions (admin function) */ try { - // Clear service worker subscription - const existingSubscription = await this.serviceWorkerRegistration.pushManager.getSubscription(); - if (existingSubscription) { - await existingSubscription.unsubscribe(); - } - - // Update status - this.isSubscribed = false; + // Call the server to clear ALL subscriptions from datastore + const response = await fetch('/clear-all-browser-notifications', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': document.querySelector('input[name=csrf_token]')?.value + } + }); - console.log('All notifications cleared'); - alert('All browser notifications have been cleared. You can now subscribe again.'); + if (response.ok) { + const result = await response.json(); + console.log('Server response:', result.message); + + // Also clear the current browser's subscription if it exists + const existingSubscription = await this.serviceWorkerRegistration.pushManager.getSubscription(); + if (existingSubscription) { + await existingSubscription.unsubscribe(); + console.log('Cleared current browser subscription'); + } + + // Update status + this.isSubscribed = false; + + alert(result.message + '. All browser notifications have been cleared.'); + } else { + const error = await response.json(); + console.error('Server clear failed:', error.message); + alert('Failed to clear server subscriptions: ' + error.message); + } } catch (error) { console.error('Failed to clear all notifications:', error); - alert('Failed to clear notifications. Please manually clear them in browser settings.'); + alert('Failed to clear notifications: ' + error.message); } }