Initial commit

This commit is contained in:
Ronald Huynen
2026-03-23 21:37:59 +01:00
commit 2547717edb
2193 changed files with 972171 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
// Enhanced presence tracker with AJAX fallback
class PresenceTracker {
constructor() {
this.heartbeatInterval = null;
this.offlineTimeout = null;
this.lastActivity = Date.now();
this.isOnline = true;
this.init();
}
init() {
this.setupActivityTracking();
this.setupVisibilityHandling();
this.setupBeforeUnload();
this.startHeartbeat();
}
setupActivityTracking() {
const events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'];
events.forEach(event => {
document.addEventListener(event, () => {
this.updateActivity();
}, { passive: true });
});
}
setupVisibilityHandling() {
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.handleUserAway();
} else {
this.handleUserBack();
}
});
}
setupBeforeUnload() {
window.addEventListener('beforeunload', () => {
this.setOffline();
});
window.addEventListener('pagehide', () => {
this.setOffline();
});
}
updateActivity() {
const now = Date.now();
if (now - this.lastActivity > 15000) { // 15 seconds
this.lastActivity = now;
if (this.offlineTimeout) {
clearTimeout(this.offlineTimeout);
}
// Set offline timeout for 5 minutes of inactivity
this.offlineTimeout = setTimeout(() => {
this.setOffline();
}, 300000);
// Update Livewire components
this.notifyLivewireComponents('handleUserActivity');
}
}
handleUserAway() {
// Set shorter offline timeout when tab is hidden
if (this.offlineTimeout) {
clearTimeout(this.offlineTimeout);
}
this.offlineTimeout = setTimeout(() => {
this.setOffline();
}, 30000); // 30 seconds when tab is hidden
}
handleUserBack() {
this.lastActivity = Date.now();
this.heartbeat(); // Immediate heartbeat when coming back
// Reset normal offline timeout
if (this.offlineTimeout) {
clearTimeout(this.offlineTimeout);
}
this.offlineTimeout = setTimeout(() => {
this.setOffline();
}, 300000); // 5 minutes
}
startHeartbeat() {
// Send heartbeat every 30 seconds
this.heartbeatInterval = setInterval(() => {
if (!document.hidden && this.isOnline) {
this.heartbeat();
}
}, 30000);
}
heartbeat() {
const guards = this.getActiveGuards();
guards.forEach(guard => {
fetch('/presence/heartbeat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content
},
body: JSON.stringify({ guard: guard })
}).then(response => {
if (response.ok) {
this.isOnline = true;
this.notifyLivewireComponents('handleUserActivity');
}
}).catch(() => {});
});
}
setOffline() {
if (!this.isOnline) return; // Already offline
const guards = this.getActiveGuards();
guards.forEach(guard => {
// Use sendBeacon for reliability during page unload
const data = JSON.stringify({ guard: guard });
if (navigator.sendBeacon) {
navigator.sendBeacon('/presence/offline', data);
} else {
fetch('/presence/offline', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content
},
body: data
}).catch(() => {});
}
});
this.isOnline = false;
this.notifyLivewireComponents('handleUserOffline');
}
getActiveGuards() {
const guards = new Set();
document.querySelectorAll('.user-presence-container').forEach(container => {
const guard = container.getAttribute('data-guard') || 'web';
guards.add(guard);
});
return Array.from(guards);
}
notifyLivewireComponents(method) {
document.querySelectorAll('[wire\\:id]').forEach(component => {
const componentId = component.getAttribute('wire:id');
if (componentId && component.classList.contains('user-presence-container')) {
try {
Livewire.find(componentId).call(method);
} catch (e) {
// component not available
}
}
});
}
destroy() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
}
if (this.offlineTimeout) {
clearTimeout(this.offlineTimeout);
}
this.setOffline();
}
}
// Initialize presence tracker
document.addEventListener('DOMContentLoaded', () => {
window.presenceTracker = new PresenceTracker();
});
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
if (window.presenceTracker) {
window.presenceTracker.destroy();
}
});
export default PresenceTracker;