feat: fix push notifications and enhance PWA experience
- Fix InvalidCharacterError in push notifications with proper VAPID key validation - Add attractive PWA install prompt component with cross-browser support - Make notification settings always visible regardless of queue status - Implement PWA install manager with user engagement detection - Use SvelteKit navigation APIs instead of browser history API - Add comprehensive error handling and logging - Include cross-browser compatibility and responsive design - Add development tooling improvements Fixes push notification bugs and significantly improves PWA user experience with modern, accessible interface components and proper error handling.
This commit is contained in:
@@ -302,26 +302,61 @@ class PushNotificationManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert VAPID key to Uint8Array
|
||||
* Convert URL-safe base64 string to Uint8Array
|
||||
* Enhanced with validation and error handling for VAPID keys
|
||||
* SSR-safe: uses window.atob only in browser context
|
||||
*/
|
||||
private urlBase64ToUint8Array(base64String: string): Uint8Array {
|
||||
if (!browser) {
|
||||
return new Uint8Array(0);
|
||||
}
|
||||
|
||||
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||
const base64 = (base64String + padding)
|
||||
.replace(/-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
|
||||
const rawData = window.atob(base64);
|
||||
const outputArray = new Uint8Array(rawData.length);
|
||||
|
||||
for (let i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i);
|
||||
// Input validation
|
||||
if (!base64String || typeof base64String !== 'string') {
|
||||
console.error('[PushManager] Invalid VAPID key: empty or non-string');
|
||||
return new Uint8Array(0);
|
||||
}
|
||||
|
||||
// Remove whitespace and validate format
|
||||
const cleanKey = base64String.trim();
|
||||
if (cleanKey.length === 0) {
|
||||
console.error('[PushManager] Invalid VAPID key: empty string');
|
||||
return new Uint8Array(0);
|
||||
}
|
||||
|
||||
// VAPID keys should be 65 characters (unpadded base64)
|
||||
if (cleanKey.length !== 65) {
|
||||
console.warn(`[PushManager] VAPID key length ${cleanKey.length}, expected 65`);
|
||||
}
|
||||
|
||||
try {
|
||||
// Add proper padding
|
||||
const padding = '='.repeat((4 - cleanKey.length % 4) % 4);
|
||||
const base64 = (cleanKey + padding)
|
||||
.replace(/-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
|
||||
// Validate base64 format before decoding
|
||||
const base64Regex = /^[A-Za-z0-9+\/]*={0,2}$/;
|
||||
if (!base64Regex.test(base64)) {
|
||||
throw new Error('Invalid base64 characters');
|
||||
}
|
||||
|
||||
const rawData = window.atob(base64);
|
||||
const outputArray = new Uint8Array(rawData.length);
|
||||
|
||||
for (let i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i);
|
||||
}
|
||||
|
||||
console.log(`[PushManager] Successfully decoded VAPID key (${outputArray.length} bytes)`);
|
||||
return outputArray;
|
||||
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.error('[PushManager] Failed to decode VAPID key:', error, 'Key:', cleanKey);
|
||||
throw new Error(`Invalid VAPID key format: ${errorMessage}`);
|
||||
}
|
||||
return outputArray;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user