Przejdź do głównej treści
Grafika przedstawia ukryty obrazek

Nowoczesny system detekcji online/idle/offline w PHP i JavaScript — kompletny przewodnik

Zdjecie zwiazane z Nowoczesny system detekcji onlineidleoffline w PHP i JavaScript  kompletny przewo

Monitoring aktywności użytkownika w aplikacji webowej to dziś standard: komunikatory, CRM-y, intranety, systemy wsparcia, platformy e-learningowe — wszystkie potrzebują aktualnej informacji, czy użytkownik jest online, idle (bezczynny) czy offline.

Wiele implementacji jest jednak błędnych lub nieefektywnych: za rzadkie pingowanie, brak detekcji realnej aktywności, niepoprawne trackowanie kart, opóźnienia sięgające minut. W tym artykule pokazuję kompletny i praktyczny sposób wdrożenia takiego systemu, który:

  • nie przeciąża serwera,
  • wykrywa realną aktywność,
  • działa natychmiast po wejściu na stronę,
  • radzi sobie z wieloma otwartymi kartami,
  • pozwala łatwo określić status usera w PHP.

Przedstawię również najczęstsze błędy i pułapki, które w takich systemach pojawiają się najczęściej — wraz z gotowymi rozwiązaniami.

1. Założenia i architektura rozwiązania

System opiera się na trzech fundamentach:

1) Detekcja aktywności użytkownika w JavaScript

Monitorujemy:

  • ruch myszki,
  • klawiaturę,
  • kliknięcia,
  • scroll,
  • wejście na kartę (visibilitychange),
  • aktywność mobilną.

Każde takie zdarzenie aktualizuje timestamp.

2) Heartbeat / ping wysyłany tylko wtedy, gdy użytkownik jest aktywny

Nie wysyłamy ciągłego „zegarowego” pingu co X sekund. Wysyłamy go tylko, jeśli użytkownik wykazuje aktywność.

3) PHP ustala status użytkownika na podstawie timestampu

Na backendzie przechowujemy last_activity i obliczamy status:

  • ONLINE: ostatnia aktywność < 2 min
  • IDLE: 2–5 min
  • OFFLINE: > 5 min

Daje to pełną kontrolę po stronie serwera.

2. Finalna wersja skryptu online.js

Plik może być hostowany lokalnie lub na CDN. Obsługuje parametry przekazywane przez data-*.

online.js

const script = document.currentScript;
const uri = script.dataset.uri;
const token = script.dataset.token;
const interval = Number(script.dataset.interval);
const idleLimit = Number(script.dataset.idle);

let lastActive = Date.now();

// Rejestrujemy aktywność
['mousemove', 'keydown', 'click', 'scroll', 'touchstart'].forEach(event => {
    window.addEventListener(event, () => {
        lastActive = Date.now();
    });
});

// Powrót na kartę = aktywność
document.addEventListener("visibilitychange", () => {
    if (document.visibilityState === "visible") {
        lastActive = Date.now();
    }
});

// Pierwszy ping natychmiast po starcie
systemSetUserActive(uri, token);

// Heartbeat wysyłany tylko przy aktywności
setInterval(() => {
    if (Date.now() - lastActive < idleLimit) {
        systemSetUserActive(uri, token);
    }
}, interval);

3. Wstawianie skryptu na stronie HTML

Kluczowe: skrypt musi być ładowany po axiosie i po funkcji systemSetUserActive().

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="/js/system.js"></script>

<script src="/js/online.js"
        defer
        data-uri="/"
        data-token="1909ed1ea11248e646985fcd1cd0d108aed2e29f"
        data-interval="30000"
        data-idle="120000"></script>

Użycie defer gwarantuje:

  • poprawną kolejność wykonywania,
  • że dokument HTML jest sparsowany,
  • bez konieczności używania DOMContentLoaded.

4. Funkcja systemSetUserActive() w JavaScript

Może wyglądać tak:

function systemSetUserActive(uri, token) {
    axios.post(uri + 'user/heartbeat', { token })
        .catch(err => console.error('Heartbeat error:', err));
}

Do backendu wysyłamy minimalne dane: sam token. Reszta (np. powiązanie tokenu z użytkownikiem) musi być walidowana na serwerze.

5. Backend — zapis timestampu (PHP)

Przykład w Laravelu, ale logika jest uniwersalna:

public function heartbeat(Request $request)
{
    $user = User::where('heartbeat_token', $request->token)->firstOrFail();

    $user->last_activity = now();
    $user->save();

    return response()->json(['status' => 'ok']);
}

W bazie trzymamy kolumnę:

last_activity  DATETIME

6. Obliczanie statusu online/idle/offline w PHP

public function getStatusAttribute()
{
    $diff = now()->diffInSeconds($this->last_activity);

    if ($diff < 120) {
        return 'online';
    } elseif ($diff < 300) {
        return 'idle';
    } else {
        return 'offline';
    }
}

Przykładowe wartości:

  • 120s → online
  • 300s → idle
  • większe → offline

7. Jak to działa w praktyce (flow logiczny)

  1. Użytkownik wchodzi na stronę.
  2. online.js natychmiast wysyła pierwszy ping → user = online.
  3. Użytkownik wykonuje jakikolwiek ruch → aktualizujemy timestamp.
  4. Jeśli w ciągu 2 minut wystąpi aktywność → co 30s wysyłany jest ping.
  5. Jeśli użytkownik nic nie robi → ping przestaje się wysyłać.
  6. Backend widzi upływ czasu → zmienia status na idle → później offline.

Brak potrzeby websocketów, a system jest lekki, szybki, precyzyjny.

8. Najczęstsze błędy i pułapki

To jeden z najważniejszych rozdziałów — większość błędnych implementacji wynika z problemów opisanych poniżej.

1. Heartbeat wysyłany zawsze, nawet gdy użytkownik jest nieaktywny

Skutek:

  • niepotrzebne obciążenie serwera,
  • fałszywe statusy online.

Rozwiązanie: Ping warunkowy zależny od aktywności (jak w tym artykule).

2. Pierwszy ping opóźniony o 30–60 sekund

Bez „initial ping” status online jest spóźniony.

Rozwiązanie: Natychmiastowe wywołanie systemSetUserActive() po starcie skryptu.

3. Zbyt uboga lista eventów aktywności

Często pomija się:

  • scroll,
  • touchstart,
  • visibilitychange.

Skutki: Urządzenia mobilne i przełączanie kart są błędnie obsługiwane.

4. Brak konwersji data-* na liczby

script.dataset.interval zwraca string, nie liczbę.

Rozwiązanie:

Number(script.dataset.interval)

5. Ładowanie skryptu przed axiosem lub systemSetUserActive()

Skutkuje błędami typu: systemSetUserActive is not defined.

Rozwiązanie: Kolejność skryptów lub defer.

6. Zbyt rzadkie lub zbyt częste pingowanie

Zbyt rzadko → opóźnione statusy. Zbyt często → zbędne obciążenie.

Rekomendacja:

  • ping = 30 sekund,
  • idle = 120–300 sekund.

7. Brak backendowej logiki statusów

Poleganie wyłącznie na tym, co wyśle frontend, jest błędne.

Backend musi obliczać status na podstawie czasu — zawsze.

8. Problemy z wieloma kartami

Każda karta wysyła własny heartbeat.

To normalne — nie próbujemy tego eliminować. Status obliczamy na backendzie → po braku aktywności we wszystkich kartach user przejdzie w idle/offline naturalnie.

9. trackowanie mousemove z dużym obciążeniem

Często w event handlerach wykonywane są ciężkie operacje. W naszym systemie to tylko aktualizacja timestampu → minimalne obciążenie.

9. Podsumowanie

System opisany w artykule:

  • jest lekki i wydajny,
  • pozwala dokładnie wykrywać stany użytkownika,
  • działa poprawnie przy wielu kartach,
  • nie wymaga WebSocketów,
  • działa na każdym urządzeniu i przeglądarce,
  • jest w pełni konfigurowalny przez data-*.

W efekcie otrzymujemy profesjonalny, stabilny i skalowalny system statusów online, który można wdrożyć w każdym projekcie PHP + JavaScript.

24 listopada 2025 5

Kategorie

programowanie

Dziękujemy!
()

Powiązane wpisy


Informacja o cookies

Moja strona internetowa wykorzystuje wyłącznie niezbędne pliki cookies, które są wymagane do jej prawidłowego działania. Nie używam ciasteczek w celach marketingowych ani analitycznych. Korzystając z mojej strony, wyrażasz zgodę na stosowanie tych plików. Możesz dowiedzieć się więcej w mojej polityce prywatności.