fix(ios): prevent chat crash when Notifications API is unavailable on iPhones

- Guarded all Notification API usage to avoid ReferenceError on iOS Safari.
- Set default permission to 'denied' when Notification is undefined.
- Added early return in notification flow when Notifications API is unavailable.
- Wrapped Notification.permission, requestPermission(), and new Notification(...) with typeof checks.
- Updated SecureNotificationManager and app.jsx to degrade gracefully.
- Verified build passes and chat loads correctly on iOS without notifications.
This commit is contained in:
lockbitchat
2025-10-17 03:49:33 -04:00
parent 1acbc12a92
commit 5ddfd1f5b3
7 changed files with 99 additions and 15 deletions

View File

@@ -317,10 +317,12 @@
}
// Check current permission status
const currentPermission = Notification.permission;
const currentPermission = (typeof Notification !== 'undefined' && Notification)
? Notification.permission
: 'denied';
// Only request if permission is default (not granted or denied)
if (currentPermission === 'default') {
if (typeof Notification !== 'undefined' && currentPermission === 'default') {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
@@ -337,9 +339,10 @@
// Handle error silently
}
// Send welcome notification
// Send welcome notification (only if Notifications API exists)
setTimeout(() => {
try {
if (typeof Notification === 'undefined') return;
const welcomeNotification = new Notification('SecureBit Chat', {
body: 'Notifications enabled! You will receive alerts for new messages.',
icon: '/logo/icon-192x192.png',
@@ -360,7 +363,7 @@
}, 1000);
}
} else if (currentPermission === 'granted') {
} else if (typeof Notification !== 'undefined' && currentPermission === 'granted') {
// Initialize notification integration immediately
try {
if (window.NotificationIntegration && webrtcManagerRef.current && !notificationIntegrationRef.current) {
@@ -377,6 +380,7 @@
// Test notification to confirm it works
setTimeout(() => {
try {
if (typeof Notification === 'undefined') return;
const testNotification = new Notification('SecureBit Chat', {
body: 'Notifications are working! You will receive alerts for new messages.',
icon: '/logo/icon-192x192.png',
@@ -1900,7 +1904,7 @@
);
// Initialize notification integration if permission was already granted
if (Notification.permission === 'granted' && window.NotificationIntegration && !notificationIntegrationRef.current) {
if (typeof Notification !== 'undefined' && Notification.permission === 'granted' && window.NotificationIntegration && !notificationIntegrationRef.current) {
try {
const integration = new window.NotificationIntegration(webrtcManagerRef.current);
integration.init().then(() => {
@@ -2926,15 +2930,26 @@
let offer;
try {
console.log('DEBUG: Processing offer input:', offerInput.trim().substring(0, 100) + '...');
console.log('DEBUG: decodeAnyPayload available:', typeof window.decodeAnyPayload === 'function');
console.log('DEBUG: decompressIfNeeded available:', typeof window.decompressIfNeeded === 'function');
// Prefer binary decode first, then gzip JSON
if (typeof window.decodeAnyPayload === 'function') {
console.log('DEBUG: Using decodeAnyPayload...');
const any = window.decodeAnyPayload(offerInput.trim());
console.log('DEBUG: decodeAnyPayload result type:', typeof any);
console.log('DEBUG: decodeAnyPayload result:', any);
offer = (typeof any === 'string') ? JSON.parse(any) : any;
} else {
console.log('DEBUG: Using decompressIfNeeded...');
const rawText = (typeof window.decompressIfNeeded === 'function') ? window.decompressIfNeeded(offerInput.trim()) : offerInput.trim();
console.log('DEBUG: decompressIfNeeded result:', rawText.substring(0, 100) + '...');
offer = JSON.parse(rawText);
}
console.log('DEBUG: Final offer:', offer);
} catch (parseError) {
console.error('DEBUG: Parse error:', parseError);
throw new Error(`Invalid invitation format: ${parseError.message}`);
}

View File

@@ -1204,12 +1204,22 @@ class EnhancedSecureCryptoUtils {
// Verify ECDSA signature (P-384 or P-256)
static async verifySignature(publicKey, signature, data) {
try {
console.log('DEBUG: verifySignature called with:', {
publicKey: publicKey,
signature: signature,
data: data
});
const encoder = new TextEncoder();
const dataBuffer = typeof data === 'string' ? encoder.encode(data) : data;
const signatureBuffer = new Uint8Array(signature);
console.log('DEBUG: verifySignature dataBuffer:', dataBuffer);
console.log('DEBUG: verifySignature signatureBuffer:', signatureBuffer);
// Try SHA-384 first, fallback to SHA-256
try {
console.log('DEBUG: Trying SHA-384 verification...');
const isValid = await crypto.subtle.verify(
{
name: 'ECDSA',
@@ -1220,6 +1230,8 @@ class EnhancedSecureCryptoUtils {
dataBuffer
);
console.log('DEBUG: SHA-384 verification result:', isValid);
EnhancedSecureCryptoUtils.secureLog.log('info', 'Signature verification completed (SHA-384)', {
isValid,
dataSize: dataBuffer.length
@@ -1227,8 +1239,10 @@ class EnhancedSecureCryptoUtils {
return isValid;
} catch (sha384Error) {
console.log('DEBUG: SHA-384 verification failed, trying SHA-256:', sha384Error);
EnhancedSecureCryptoUtils.secureLog.log('warn', 'SHA-384 verification failed, trying SHA-256', { error: sha384Error.message });
console.log('DEBUG: Trying SHA-256 verification...');
const isValid = await crypto.subtle.verify(
{
name: 'ECDSA',
@@ -1239,6 +1253,8 @@ class EnhancedSecureCryptoUtils {
dataBuffer
);
console.log('DEBUG: SHA-256 verification result:', isValid);
EnhancedSecureCryptoUtils.secureLog.log('info', 'Signature verification completed (SHA-256 fallback)', {
isValid,
dataSize: dataBuffer.length
@@ -1583,6 +1599,12 @@ class EnhancedSecureCryptoUtils {
// Import and verify signed public key
static async importSignedPublicKey(signedPackage, verifyingKey, expectedKeyType = 'ECDH') {
try {
console.log('DEBUG: importSignedPublicKey called with:', {
signedPackage: signedPackage,
verifyingKey: verifyingKey,
expectedKeyType: expectedKeyType
});
// Validate package structure
if (!signedPackage || typeof signedPackage !== 'object') {
throw new Error('Invalid signed package format');
@@ -1609,7 +1631,11 @@ class EnhancedSecureCryptoUtils {
// Verify signature
const packageCopy = { keyType, keyData, timestamp, version };
const packageString = JSON.stringify(packageCopy);
console.log('DEBUG: Web version package string for verification:', packageString);
console.log('DEBUG: Web version signature to verify:', signature);
console.log('DEBUG: Web version verifying key:', verifyingKey);
const isValidSignature = await EnhancedSecureCryptoUtils.verifySignature(verifyingKey, signature, packageString);
console.log('DEBUG: Web version signature verification result:', isValidSignature);
if (!isValidSignature) {
throw new Error('Invalid signature on key package - possible MITM attack');

View File

@@ -9,7 +9,10 @@
class SecureChatNotificationManager {
constructor(config = {}) {
this.permission = Notification.permission;
// Safely read Notification permission (iOS Safari may not define Notification)
this.permission = (typeof Notification !== 'undefined' && Notification && typeof Notification.permission === 'string')
? Notification.permission
: 'denied';
this.isTabActive = this.checkTabActive(); // Initialize with proper check
this.unreadCount = 0;
this.originalTitle = document.title;
@@ -238,6 +241,10 @@ class SecureChatNotificationManager {
* @returns {Notification|null} Created notification or null
*/
notify(senderName, message, options = {}) {
// Abort if Notifications API is not available (e.g., iOS Safari)
if (typeof Notification === 'undefined') {
return null;
}
// Update tab active state before checking
this.isTabActive = this.checkTabActive();