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 10

Kategorie

programowanie

Dziękujemy!
()

Powiązane wpisy

Grafika przedstawia Automatyczny motyw Bootstrap 53 na bazie pory dnia
4 stycznia 2025 6 min 71

Automatyczny motyw Bootstrap 5.3 na bazie pory dnia

php
Czytaj więcej
Zdjecie zwiazane z Long Polling vs Short Polling Porwnanie
21 stycznia 2025 5 min 26

Long Polling vs. Short Polling: Porównanie

php
Czytaj więcej
Zdjecie zwiazane z Wyraenia regularne i ich obsuga w PHP oraz JavaScript
1 lutego 2025 5 min 19

Wyrażenia regularne i ich obsługa w PHP oraz JavaScript

php
Czytaj więcej
Wymiana doświadczeń

Masz podobne doświadczenia?

Chętnie poznam Twoją perspektywę i porozmawiam o tym temacie szerzej.

Napisz do mnie

Każda perspektywa może wnieść coś wartościowego do dyskusji.

Twoja prywatność i pliki cookies

  1. Ta strona internetowa wykorzystuje wyłącznie niezbędne pliki cookies, które są wymagane do jej prawidłowego działania – m.in. do poprawnego wyświetlania treści, zapamiętania podstawowych ustawień przeglądarki oraz zapewnienia stabilności serwisu.
  2. Nie stosuję plików cookies w celach marketingowych, reklamowych ani analitycznych.
  3. Strona ma charakter wyłącznie informacyjny i nie zawiera formularzy kontaktowych, rejestracyjnych ani zakupowych, przez które dane mogłyby być przesyłane na serwer.
  4. Nie zbieram danych osobowych podczas zwykłego korzystania z witryny.
  5. Serwis nie korzysta z certyfikatu SSL, jednak ze względu na informacyjny charakter strony nie jest wymagane przesyłanie poufnych danych. Zalecam jednak, aby nigdy nie wpisywać haseł ani danych osobowych na stronach bez szyfrowanego połączenia.
  6. Korzystając z tej strony, wyrażasz zgodę na używanie wyłącznie niezbędnych plików cookies.

Więcej informacji znajdziesz w mojej polityce prywatności.