response->setJSON([ 'success' => true, 'timeout' => SessionTimeout::TIMEOUT_DURATION, 'warning_time' => SessionTimeout::TIMEOUT_DURATION - SessionTimeout::WARNING_THRESHOLD, 'check_interval' => SessionTimeout::CLIENT_CHECK_INTERVAL, 'ping_interval' => SessionTimeout::CLIENT_PING_INTERVAL, 'logout_url' => site_url('logout'), 'keep_alive_url' => site_url('session/ping-activity'), 'check_url' => site_url('session/check-timeout'), ]); } public function checkTimeout() { try { $session = session(); if (! $session->has('last_activity')) { return $this->expireSession(); } $lastActivity = (int) $session->get('last_activity'); $elapsed = time() - $lastActivity; if ($elapsed >= SessionTimeout::TIMEOUT_DURATION) { return $this->expireSession(); } if ($elapsed >= SessionTimeout::WARNING_THRESHOLD) { return $this->response->setJSON([ 'status' => 'warning', 'time_remaining' => SessionTimeout::TIMEOUT_DURATION - $elapsed, ]); } return $this->response->setJSON([ 'status' => 'active', 'time_remaining' => SessionTimeout::TIMEOUT_DURATION - $elapsed, ]); } catch (Throwable $e) { $this->logDbFailureOnce('session/check-timeout', $e); return $this->response ->setStatusCode(503) ->setJSON([ 'status' => 'error', 'message' => 'Session check temporarily unavailable.', ]); } } public function pingActivity() { try { $session = session(); if (! $session->has('last_activity') || (time() - (int) $session->get('last_activity') >= SessionTimeout::TIMEOUT_DURATION) ) { return $this->expireSession(); } $session->set('last_activity', time()); return $this->response->setJSON([ 'status' => 'active', 'time_remaining' => SessionTimeout::TIMEOUT_DURATION, ]); } catch (Throwable $e) { $this->logDbFailureOnce('session/ping-activity', $e); return $this->response ->setStatusCode(503) ->setJSON([ 'status' => 'error', 'message' => 'Activity ping temporarily unavailable.', ]); } } private function expireSession() { $this->destroySession(); return $this->response->setJSON([ 'status' => 'expired', 'redirect' => site_url('login'), 'message' => 'Your session has expired due to inactivity.', ]); } private function destroySession(): void { try { $session = session(); $session->setFlashdata('error', 'Your session has expired due to inactivity.'); $session->remove('last_activity'); $session->destroy(); if (session_status() === PHP_SESSION_ACTIVE) { session_regenerate_id(true); } } catch (Throwable $e) { $this->logDbFailureOnce('session/destroy', $e); } } private function logDbFailureOnce(string $endpoint, Throwable $e): void { static $logged = []; $key = $endpoint . '|' . get_class($e); if (isset($logged[$key])) { return; } $logged[$key] = true; log_message('error', 'Session endpoint failure on {endpoint}: {message}', [ 'endpoint' => $endpoint, 'message' => $e->getMessage(), ]); } }