class VisitorTracker { constructor() { this.pingInterval = null; this.isReloading = false; this.isNavigating = false; this.isActive = true; this.lastActivity = Date.now(); this.tabHiddenTime = null; this.removeTimeout = null; this.userId = null; this.init(); } init() { this.trackInitialVisit(); this.startPing(); this.setupActivityTracking(); this.setupReloadDetection(); this.setupVisibilityTracking(); this.setupNavigationDetection(); this.setupBeforeUnload(); } trackInitialVisit() { // İlk ziyareti kaydet ve user ID al fetch('/ajax/visitor_logger.php') .then(response => response.json()) .then(data => { if (data.user_id) { this.userId = data.user_id; } }) .catch(() => {}); } startPing() { // 27 saniyede bir ping — chat.js (25s) ile aynı anda tetiklenmesin this.pingInterval = setInterval(() => { if (!document.hidden) { this.sendPing(); } }, 27000); // İlk ping'i 3 sn sonra (chat'in ilk ping'i ile çakışmasın) setTimeout(() => this.sendPing(), 3000); } sendPing() { fetch('/ajax/visitor_logger.php?action=ping') .then(response => response.json()) .then(data => { // User ID'yi güncelle (her zaman güncel olsun) if (data.user_id) { this.userId = data.user_id; } const activeCount = data.active_count || 0; this.updateActiveUserDisplay(activeCount); }) .catch(() => {}); } updateActiveUserDisplay(count) { const activeUsersElement = document.getElementById('active-users'); if (activeUsersElement) { const formattedCount = count >= 1000 ? (count / 1000).toFixed(1) + 'K' : count.toString(); activeUsersElement.textContent = formattedCount; } } setupActivityTracking() { // Yüksek frekanslı olaylar throttle ile — lastActivity sadece 200ms'de bir güncellenir const throttleMs = 200; let _lastUpdate = 0; const markActive = () => { const now = Date.now(); if (now - _lastUpdate < throttleMs) return; _lastUpdate = now; this.lastActivity = now; this.isActive = true; }; // mousemove / scroll: çok sık tetiklenen olaylar — throttle ['mousemove', 'scroll'].forEach(event => { document.addEventListener(event, markActive, { passive: true }); }); // Düşük frekanslı olaylar: doğrudan (throttle gereksiz) ['mousedown', 'keypress', 'touchstart', 'click'].forEach(event => { document.addEventListener(event, () => { this.lastActivity = Date.now(); this.isActive = true; }, { passive: true }); }); } setupReloadDetection() { // F5 ve Ctrl+R ile reload tespiti document.addEventListener('keydown', (e) => { if (e.key === 'F5' || (e.ctrlKey && (e.key === 'r' || e.key === 'R'))) { this.isReloading = true; } }); // Tarayıcı yenileme butonu tespiti window.addEventListener('beforeunload', (e) => { // Performance API ile reload kontrolü if (performance.navigation && performance.navigation.type === 1) { this.isReloading = true; } }); } setupNavigationDetection() { // Link tıklamalarını takip et document.addEventListener('click', (e) => { const link = e.target.closest('a'); if (link && link.href && !link.target && !link.download) { if (link.href.startsWith(window.location.origin)) { this.isNavigating = true; this.isReloading = true; } } }, true); document.addEventListener('submit', () => { this.isNavigating = true; this.isReloading = true; }, true); } setupVisibilityTracking() { // Sekme görünürlük değişimlerini takip et document.addEventListener('visibilitychange', () => { if (document.hidden) { this.tabHiddenTime = Date.now(); if (this.removeTimeout) { clearTimeout(this.removeTimeout); this.removeTimeout = null; } this.removeTimeout = setTimeout(() => { if (document.hidden) { this.removeUser(); } }, 360000); } else { if (this.removeTimeout) { clearTimeout(this.removeTimeout); this.removeTimeout = null; } this.isActive = true; this.lastActivity = Date.now(); this.isReloading = false; this.isNavigating = false; this.sendPing(); this.tabHiddenTime = null; } }); } setupBeforeUnload() { window.addEventListener('beforeunload', (e) => { if (!this.isReloading && !this.isNavigating) { this.removeUser(); } }); window.addEventListener('pagehide', (e) => { if (!e.persisted && !this.isReloading && !this.isNavigating) { this.removeUser(); } }); } removeUser() { let url = 'ajax/visitor_logger.php?action=remove'; if (this.userId) { url += '&user_id=' + encodeURIComponent(this.userId); } if (navigator.sendBeacon) { navigator.sendBeacon(url); } else { fetch(url, { method: 'GET', keepalive: true }).catch(() => {}); } } stopTracking() { if (this.pingInterval) { clearInterval(this.pingInterval); } if (this.removeTimeout) { clearTimeout(this.removeTimeout); } this.removeUser(); } // Manuel olarak aktif durumu güncelle markAsActive() { this.isActive = true; this.lastActivity = Date.now(); this.sendPing(); } // Kullanıcının online durumunu kontrol et isUserOnline() { return this.isActive && !document.hidden && (Date.now() - this.lastActivity) < 300000; } // Anlık istatistikleri al getStats() { return fetch('/ajax/visitor_logger.php?action=ping') .then(response => response.json()) .catch(() => { return null; }); } // User ID'yi al getUserId() { return this.userId; } } // Global değişken olarak tanımla window.visitorTracker = null; document.addEventListener('DOMContentLoaded', () => { window.visitorTracker = new VisitorTracker(); // Debug için global fonksiyonlar window.getActiveUserCount = () => { if (window.visitorTracker) { window.visitorTracker.getStats().then(stats => { }); } }; window.forceRemoveUser = () => { if (window.visitorTracker) { window.visitorTracker.removeUser(); } }; window.forcePing = () => { if (window.visitorTracker) { window.visitorTracker.sendPing(); } }; window.checkTrackerStatus = () => { }; window.showActiveUsers = () => { fetch('/ajax/visitor_logger.php?action=ping') .then(response => response.json()) .then(data => { }); }; });