Basit Anlatım (Herkes İçin)
Ne yapıldı, neden yapıldı, nasıl çalışıyor?
Sistem Ne Yapıyor?
Muzibu.com.tr'de bir kullanıcı hesabına aynı anda sadece 1 cihazdan giriş yapılabilir. Kullanıcı telefondan giriş yaptıktan sonra bilgisayardan tekrar giriş yaparsa, telefonundaki oturum otomatik kapanır.
Günlük Hayattan Örnek: Sanki banka hesabınız gibi - aynı anda hem ATM'den hem de bankacılık uygulamasından para çekemezsiniz. Son giriş yapan kazanır, önceki oturum kapanır.
Kullanıcı Deneyimi
- 1. Kullanıcı telefondan giriş yapar → Müzik dinlemeye başlar
- 2. Aynı kullanıcı bilgisayardan giriş yapar → Yeni oturum açılır
- 3. Telefonundaki müzik aniden durur → "Başka bir cihazdan giriş yapıldı" uyarısı çıkar
- 4. Telefon otomatik çıkış yapar → Tekrar giriş yapması gerekir
Neden Önemli?
Güvenlik
Şifre paylaşımı engellenir. Hesabı çalan birisi giriş yaparsa, gerçek kullanıcı anında fark eder.
Gelir Artışı
Premium üyelik satan platformlar için kritik. Kullanıcı sadece kendi hesabını kullanabilir, arkadaşlarıyla paylaşamaz.
Sistem Performansı
Sunucu yükü azalır. Aynı anda 10.000 kullanıcı varsa, max 10.000 aktif oturum vardır, daha fazla değil.
Kullanıcı Kontrol
Kullanıcı hangi cihazlardan bağlı olduğunu görür, istediğini kapatabilir (gelecek özellik).
Teknik Detaylar (Geliştiriciler İçin)
Dosya yapısı, mimari, algoritma, teknolojiler
Sistem Mimarisi
Database Layer
-
•
user_active_sessionstablosu (tenant DB) - • Session ID, IP, User Agent, Device Type
- • Login Token (cookie ile sync - Livewire regenerate için)
- • Last Activity timestamp (stale session cleanup)
Backend Layer
- • Laravel Session Driver (Redis/Database/File)
-
•
DeviceService(session tracking) -
•
AuthController(login/logout API) - • Sanctum stateful authentication (web guard)
Frontend Layer
-
•
MuzibuSessionJavaScript module -
•
Polling interval:
5 saniye(test) /5 dakika(canlı) -
•
Fetch API (
/api/auth/check-session) - • Session termination modal (automatic logout)
Settings Integration
-
•
auth_subscription= true (subscription sistemi) -
•
auth_device= true (device limit aktif) -
•
auth_device_limit= 1 (tenant fallback) - • User/Plan override supported (3-tier hierarchy)
LIFO (Last In, First Out) Algoritması
Yeni giriş yapıldığında en eski oturum otomatik silinir. Kullanıcı limit aşarsa sistem en eski session'ı temizler.
// DeviceService.php:468 - enforceDeviceLimit()
protected function enforceDeviceLimit(User $user, string $currentSessionId): void
{
$limit = $this->getDeviceLimit($user); // 1 (default)
$sessions = DB::table($this->table)
->where('user_id', $user->id)
->orderBy('last_activity', 'asc') // En eski önce (LIFO)
->get();
$count = $sessions->count();
if ($count > $limit) {
$toRemove = $count - $limit; // Kaç session silinecek
// En eski session'ları sil (mevcut hariç)
$oldSessions = $sessions
->filter(fn($s) => $s->session_id !== $currentSessionId)
->take($toRemove);
foreach ($oldSessions as $old) {
DB::table($this->table)->where('id', $old->id)->delete();
// Bu session'a ait kullanıcı polling ile fark eder
}
}
}
Session Tracking (Login Token Approach)
Livewire session ID'yi regenerate edebilir, bu yüzden login_token kullanılır (cookie + DB).
1. Login İşlemi (registerSession)
// DeviceService.php:43 - registerSession()
$loginToken = bin2hex(random_bytes(32)); // 64 char benzersiz token
DB::table('user_active_sessions')->insert([
'user_id' => $user->id,
'session_id' => session()->getId(),
'login_token' => $loginToken, // 🔥 SABİT TOKEN
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
'device_type' => 'desktop|mobile|tablet',
'last_activity' => now(),
]);
// Cookie'ye 7 gün süreyle kaydet (HttpOnly, Secure)
cookie()->queue(cookie('mzb_login_token', $loginToken, 60*24*7));
// LIFO kontrolü: Limit aşılırsa en eski session'ı sil
$this->enforceDeviceLimit($user, session()->getId());
2. Session Kontrolü (sessionExists)
// DeviceService.php:186 - sessionExists()
// 1. Cookie'den login_token al
$cookieToken = request()->cookie('mzb_login_token');
// 2. DB'de bu token var mı kontrol et
$session = DB::table('user_active_sessions')
->where('user_id', $user->id)
->where('login_token', $cookieToken)
->first();
if ($session) {
// Session ID değişmişse güncelle (Livewire regenerate)
if (session()->getId() !== $session->session_id) {
DB::table('user_active_sessions')
->where('id', $session->id)
->update(['session_id' => session()->getId()]);
}
return true; // ✅ Session geçerli
}
return false; // ❌ Token DB'de yok = LIFO tarafından silindi
Frontend Polling System
Her 5 saniyede (test) / 5 dakikada (canlı) bir backend'e session durumu sorulur.
// session.js:18 - startSessionPolling()
const SESSION_POLL_INTERVAL = 5000; // 5 saniye (test) | 300000 (5 dk - canlı)
// Login sonrası 2 saniye bekle (race condition önleme)
setTimeout(() => this.checkSessionValidity(), 2000);
// Periyodik kontrol başlat
this.sessionPollInterval = setInterval(() => {
this.checkSessionValidity();
}, SESSION_POLL_INTERVAL);
// session.js:58 - checkSessionValidity()
async checkSessionValidity() {
const response = await fetch('/api/auth/check-session', {
method: 'GET',
headers: {
'Accept': 'application/json',
'Referer': window.location.origin + '/', // Sanctum için zorunlu
},
credentials: 'same-origin'
});
const data = await response.json();
if (!data.valid) {
if (data.reason === 'session_terminated') {
// ⚠️ Başka cihazdan giriş yapıldı
this.handleSessionTerminated(data.message);
}
}
}
// session.js:149 - handleSessionTerminated()
handleSessionTerminated(message) {
// Polling durdur
this.stopSessionPolling();
// Müzik/oynatıcı durdur
this.stopCurrentPlayback();
// API logout çağır
fetch('/api/auth/logout', {method: 'POST'});
// Hard redirect (Livewire intercept edemez)
window.location.href = '/login?session_terminated=1';
}
Complete Flow (Step by Step)
Login (Device A - Phone)
AuthController::login() →
DeviceService::registerSession() →
DB'ye session kaydı (login_token oluştur) →
Cookie'ye token kaydet →
Frontend polling başlar
Login (Device B - Desktop)
Aynı kullanıcı tekrar giriş yapar →
Yeni session kaydı oluşur →
enforceDeviceLimit() çağrılır →
Limit 1, mevcut 2 →
En eski session (Device A) DB'den SİLİNİR
Polling (Device A)
5 saniye sonra checkSessionValidity() çağrılır →
Backend sessionExists() kontrol eder →
Cookie'de login_token var ama DB'de YOK →
return false
Automatic Logout (Device A)
Frontend "session_terminated" alır →
handleSessionTerminated() çağrılır →
Müzik durdurulur →
Polling durdurulur →
Logout API →
window.location.href = '/login?session_terminated=1'
Dosya Yapısı
Backend (PHP/Laravel)
Modules/Muzibu/app/Services/DeviceService.php- Session tracking (LIFO, registration, termination)app/Http/Controllers/Api/Auth/AuthController.php- Login/logout/checkSession endpointsapp/Http/Controllers/Auth/AuthenticatedSessionController.php- Web login (Blade + Livewire)Modules/Muzibu/app/Http/Controllers/Api/DeviceController.php- Device management APIapp/Http/Middleware/CheckDeviceLimit.php- Device limit middleware (route protection)
Frontend (JavaScript)
public/themes/muzibu/js/player/features/session.js- MuzibuSession module (polling, logout)public/themes/muzibu/js/player/core/player-core.js- Main player (session integration)public/themes/muzibu/js/player/features/api.js- API client (auth requests)
Database
database/migrations/tenant/2025_12_07_182016_create_user_active_sessions_table.php- Migrationtenant_muzibu_1528d0.user_active_sessions- Session tracking table (tenant DB)
Views (Blade Components)
resources/views/themes/muzibu/components/device-limit-modal.blade.php- Device selection modalresources/views/themes/muzibu/components/device-limit-warning-modal.blade.php- Warning modal
Device Limit Settings (3-Tier Hierarchy)
Device limit 3 yerden alınır (öncelik sırasıyla):
User Override
users.device_limit - Kullanıcıya özel limit (admin panelden ayarlanır)
Subscription Plan
subscription_plans.device_limit - Plan'a göre limit (Basic: 1, Premium: 3, Enterprise: 10)
Tenant Setting
setting('auth_device_limit') - Tenant genelinde default limit (varsayılan: 1)
// DeviceService.php:432 - getDeviceLimit()
public function getDeviceLimit(User $user): int
{
// 1. User override
if ($user->device_limit !== null && $user->device_limit > 0) {
return $user->device_limit;
}
// 2. Subscription Plan
$subscription = $user->subscriptions()
->whereIn('status', ['active', 'trial'])
->with('plan')
->first();
if ($subscription && $subscription->plan->device_limit) {
return (int) $subscription->plan->device_limit;
}
// 3. Tenant setting fallback
return (int) setting('auth_device_limit', 1);
}
Performans & Scalability
Polling Interval
-
⚠️
Test: 5 saniye - 10.000 kullanıcı →
2.000 req/s(sunucu patlar!) -
✅
Canlı: 5 dakika (300s) - 10.000 kullanıcı →
33 req/s(kabul edilebilir)
Database Optimization
-
•
Index:
['user_id', 'last_activity']- Fast lookup - • Stale cleanup: 60 dakikadan eski session'lar otomatik silinir
- • LIFO: Max session sayısı = aktif kullanıcı sayısı (çok verimli)
Cache Strategy
- • Session data Redis'te tutulabilir (Laravel session driver)
- • Login token cookie'de 7 gün saklanır (HttpOnly + Secure)
- • DB query: WHERE user_id + login_token (indexed, fast)
Edge Cases
- ✅ Livewire session regenerate → login_token ile handle edilir
- ✅ Network error → Polling devam eder, logout YAPILMAZ
- ✅ Race condition → 2 saniye delay (registerSession tamamlansın)
Önemli Notlar & Kısıtlamalar
Sistem Kapatılabilir
setting('auth_device') = false olursa sistem çalışmaz.
shouldRun() kontrolü her metotta var.
Tenant-Specific
Sadece Tenant 1001 (Muzibu) için aktif.
Diğer tenant'larda tenant() kontrolü ile devre dışı.
Polling Delay
Kullanıcı başka cihazdan giriş yapsa bile, eski cihaz max 5 saniye (test) / 5 dakika (canlı) içinde fark eder.
Login Token Cookie
mzb_login_token cookie'si HttpOnly + Secure.
JavaScript erişemez, sadece backend okur.