Fix alert system initialization - clean up testAlertSound function scope

This commit is contained in:
2026-01-12 13:24:28 +00:00
parent 8d99d77e7a
commit acf188b708
2 changed files with 294 additions and 0 deletions
+195
View File
@@ -0,0 +1,195 @@
/**
* User Alert System
* Monitors shared notes and alerts when someone shares a note with the current user
*/
export class UserAlertSystem {
constructor(pocketbaseInstance, userEmail, userName) {
this.pb = pocketbaseInstance;
this.userEmail = userEmail;
this.userName = userName;
this.alertedNotes = new Set(); // Track already-alerted notes to avoid duplicates
this.unsubscribe = null;
}
/**
* Play notification sound - double bell/chime
*/
async playNotificationSound() {
try {
// Create a simple double bell tone using Web Audio API
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const now = audioContext.currentTime;
// First bell
this.playBell(audioContext, now, 0);
// Second bell (offset by 200ms)
this.playBell(audioContext, now + 0.2, 0.7);
} catch (error) {
console.error('Failed to play notification sound:', error);
// Fallback: try to play a system notification beep
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(audioContext.destination);
osc.frequency.value = 800;
gain.gain.setValueAtTime(0.3, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
osc.start(audioContext.currentTime);
osc.stop(audioContext.currentTime + 0.1);
} catch (e) {
console.error('Fallback notification sound also failed:', e);
}
}
}
/**
* Play a single bell tone
*/
playBell(audioContext, startTime, volume = 1) {
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(audioContext.destination);
// Bell frequency (higher pitch for notification)
osc.frequency.setValueAtTime(1000, startTime);
osc.frequency.exponentialRampToValueAtTime(600, startTime + 0.15);
// Bell envelope (fade out)
gain.gain.setValueAtTime(0.3 * volume, startTime);
gain.gain.exponentialRampToValueAtTime(0.01, startTime + 0.2);
osc.start(startTime);
osc.stop(startTime + 0.2);
}
/**
* Check if a name matches the current user
* Searches Associations collection for first_name match and verifies email
*/
async verifyUserMatch(firstName) {
try {
console.log(`Checking for user match: firstName="${firstName}", currentEmail="${this.userEmail}"`);
// Search Associations collection for matching first_name
const records = await this.pb.collection('Associations').getList(1, 50, {
filter: `first_name = "${firstName}"`,
});
console.log(`Found ${records.items.length} records with first_name="${firstName}"`);
if (records.items.length === 0) {
console.log(`No user found with first name: ${firstName}`);
return false;
}
// Check if any matching record has the current user's email
const match = records.items.find((record) => {
console.log(`Checking record: ${record.first_name} ${record.last_name} (${record.email})`);
return record.email === this.userEmail;
});
if (match) {
console.log(`✓ User match confirmed: ${firstName} (${this.userEmail})`);
return true;
}
console.log(`No email match for ${firstName} and ${this.userEmail}`);
return false;
} catch (error) {
console.error('Error verifying user match:', error);
return false;
}
}
/**
* Initialize real-time monitoring of Notes collection
*/
async startMonitoring() {
try {
console.log('🔔 Starting user alert monitoring...');
// Subscribe to Notes collection changes
this.unsubscribe = await this.pb.collection('Notes').subscribe('*', async (event) => {
try {
const note = event.record;
console.log(`📝 Note event (${event.action}):`, note.title, `shared=${note.shared}`, `shared_with=`, note.shared_with);
// Only process shared notes
if (!note.shared) {
console.log(' → Skipped: not shared');
return;
}
// Check if current user is in shared_with list
const sharedWithNames = Array.isArray(note.shared_with)
? note.shared_with
: typeof note.shared_with === 'string'
? note.shared_with.split(',').map((n) => n.trim())
: [];
console.log(` → shared_with names:`, sharedWithNames);
// Extract first names from shared_with and check for match
let userIsRecipient = false;
for (const sharedName of sharedWithNames) {
const firstName = sharedName.split(' ')[0];
console.log(` → Checking if ${firstName} matches...`);
const isMatch = await this.verifyUserMatch(firstName);
if (isMatch) {
userIsRecipient = true;
break;
}
}
if (!userIsRecipient) {
console.log(' → Skipped: user not in shared_with');
return;
}
// Check if we've already alerted for this note
if (this.alertedNotes.has(note.id)) {
console.log(' → Skipped: already alerted for this note');
return;
}
// Mark as alerted and play sound
this.alertedNotes.add(note.id);
console.log(`🔔 Alert! Note "${note.title}" shared with you`);
await this.playNotificationSound();
} catch (error) {
console.error('Error processing note event:', error);
}
});
console.log('✓ User alert monitoring started');
} catch (error) {
console.error('Failed to start monitoring:', error);
}
}
/**
* Stop monitoring
*/
stopMonitoring() {
if (this.unsubscribe) {
this.unsubscribe();
this.unsubscribe = null;
console.log('⏹ User alert monitoring stopped');
}
}
}
/**
* Initialize user alert system
* Call this after user authentication
*/
export async function initializeUserAlert(pb, userEmail, userName) {
const alertSystem = new UserAlertSystem(pb, userEmail, userName);
await alertSystem.startMonitoring();
return alertSystem;
}