mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-09-27 07:46:22 +00:00
WIP
This commit is contained in:
@@ -11,32 +11,44 @@ from loguru import logger
|
||||
|
||||
def convert_pem_private_key_for_pywebpush(private_key):
|
||||
"""
|
||||
Convert PEM private key to the raw bytes format that pywebpush expects
|
||||
Convert PEM private key to the format that pywebpush expects
|
||||
|
||||
Args:
|
||||
private_key: PEM private key string or already converted key
|
||||
|
||||
Returns:
|
||||
Private key in the format pywebpush expects
|
||||
Private key in the format pywebpush expects (PEM string for pywebpush)
|
||||
"""
|
||||
if not isinstance(private_key, str) or not private_key.startswith('-----BEGIN'):
|
||||
# pywebpush expects the PEM string directly
|
||||
if not isinstance(private_key, str):
|
||||
return private_key
|
||||
|
||||
# If it doesn't look like PEM, return as-is
|
||||
if not private_key.startswith('-----BEGIN'):
|
||||
return private_key
|
||||
|
||||
try:
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
private_key_bytes = private_key.encode()
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
|
||||
# Validate the key by loading it
|
||||
private_key_bytes = private_key.encode('utf-8')
|
||||
private_key_obj = serialization.load_pem_private_key(private_key_bytes, password=None)
|
||||
|
||||
# Get raw private key bytes for pywebpush
|
||||
private_key_raw = private_key_obj.private_bytes(
|
||||
encoding=serialization.Encoding.Raw,
|
||||
format=serialization.PrivateFormat.Raw,
|
||||
encryption_algorithm=serialization.NoEncryption()
|
||||
)
|
||||
return private_key_raw
|
||||
# Verify it's an ECDSA key (required for VAPID)
|
||||
if not isinstance(private_key_obj, ec.EllipticCurvePrivateKey):
|
||||
logger.error("Private key is not an ECDSA key - VAPID requires ECDSA")
|
||||
return private_key
|
||||
|
||||
# Ensure the key has the right curve (P-256 for VAPID)
|
||||
if private_key_obj.curve.name != 'secp256r1':
|
||||
logger.warning(f"Private key uses curve {private_key_obj.curve.name}, VAPID recommends secp256r1 (P-256)")
|
||||
|
||||
# Return the original PEM - pywebpush handles PEM format correctly
|
||||
return private_key
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to convert private key format, using as-is: {e}")
|
||||
logger.warning(f"Failed to validate private key format: {e}")
|
||||
return private_key
|
||||
|
||||
|
||||
|
||||
@@ -159,6 +159,9 @@ class BrowserNotifications {
|
||||
return;
|
||||
}
|
||||
|
||||
// First, try to clear any existing subscription with different keys
|
||||
await this.clearExistingSubscription();
|
||||
|
||||
// Create push subscription
|
||||
const subscription = await this.serviceWorkerRegistration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
@@ -196,7 +199,13 @@ class BrowserNotifications {
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Failed to subscribe to keyword ${keyword}:`, error);
|
||||
alert(`Failed to subscribe: ${error.message}`);
|
||||
|
||||
// Show user-friendly error message
|
||||
if (error.message.includes('different applicationServerKey')) {
|
||||
this.showSubscriptionConflictDialog(keyword, error);
|
||||
} else {
|
||||
alert(`Failed to subscribe: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,6 +328,74 @@ class BrowserNotifications {
|
||||
return outputArray;
|
||||
}
|
||||
|
||||
async clearExistingSubscription() {
|
||||
/**
|
||||
* Clear any existing push subscription that might conflict with our VAPID keys
|
||||
*/
|
||||
try {
|
||||
const existingSubscription = await this.serviceWorkerRegistration.pushManager.getSubscription();
|
||||
|
||||
if (existingSubscription) {
|
||||
console.log('Found existing subscription, unsubscribing...');
|
||||
await existingSubscription.unsubscribe();
|
||||
console.log('Successfully cleared existing subscription');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to clear existing subscription:', error);
|
||||
// Don't throw - this is just cleanup
|
||||
}
|
||||
}
|
||||
|
||||
showSubscriptionConflictDialog(keyword, error) {
|
||||
/**
|
||||
* Show user-friendly dialog for subscription conflicts
|
||||
*/
|
||||
const message = `Browser notifications are already set up for a different changedetection.io instance or with different settings.
|
||||
|
||||
To fix this:
|
||||
1. Clear your existing subscription
|
||||
2. Try subscribing again
|
||||
|
||||
Would you like to automatically clear the old subscription and retry?`;
|
||||
|
||||
if (confirm(message)) {
|
||||
this.clearExistingSubscription().then(() => {
|
||||
// Retry subscription after clearing
|
||||
setTimeout(() => {
|
||||
this.subscribeToKeyword(keyword);
|
||||
}, 500);
|
||||
});
|
||||
} else {
|
||||
alert('To use browser notifications, please manually clear your browser notifications for this site in browser settings, then try again.');
|
||||
}
|
||||
}
|
||||
|
||||
async clearAllNotifications() {
|
||||
/**
|
||||
* Clear all browser notification subscriptions (admin function)
|
||||
*/
|
||||
try {
|
||||
// Clear service worker subscription
|
||||
const existingSubscription = await this.serviceWorkerRegistration.pushManager.getSubscription();
|
||||
if (existingSubscription) {
|
||||
await existingSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
// Clear local storage
|
||||
this.subscriptions.clear();
|
||||
|
||||
// Update UI
|
||||
this.updateSubscriptionsList();
|
||||
|
||||
console.log('All notifications cleared');
|
||||
alert('All browser notifications have been cleared. You can now subscribe again.');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to clear all notifications:', error);
|
||||
alert('Failed to clear notifications. Please manually clear them in browser settings.');
|
||||
}
|
||||
}
|
||||
|
||||
async handleAutoSubscription() {
|
||||
// Handle auto-subscription for keywords detected from browser:// URLs
|
||||
try {
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
<div class="pure-form-message-inline">
|
||||
<p><strong>Browser Notification Channels</strong> - These are automatically detected when you use <code>browser://keyword</code> URLs in your notification settings above.</p>
|
||||
<p>When detected, you'll be prompted to enable browser notifications for these channels so you can receive real-time push notifications even when this tab is closed.</p>
|
||||
<p><small><strong>Troubleshooting:</strong> If you get "different applicationServerKey" errors, click "Clear All Notifications" below and try again. This happens when switching between different changedetection.io instances.</small></p>
|
||||
<div id="browser-notification-controls" style="margin-top: 1em;">
|
||||
<div id="notification-permission-status">
|
||||
<p>Browser notifications: <span id="permission-status">checking...</span></p>
|
||||
@@ -52,6 +53,9 @@
|
||||
<button type="button" id="test-notification-btn" class="pure-button button-secondary button-xsmall" style="display: none;">
|
||||
Test Notification
|
||||
</button>
|
||||
<button type="button" id="clear-notifications-btn" class="pure-button button-secondary button-xsmall" onclick="window.browserNotifications?.clearAllNotifications()" style="margin-left: 0.5em;">
|
||||
Clear All Notifications
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user