Files
presensi/app/Modules/Dashboard/Controllers/RealtimeStreamController.php
2026-03-05 14:37:36 +07:00

106 lines
3.2 KiB
PHP

<?php
namespace App\Modules\Dashboard\Controllers;
use App\Core\BaseApiController;
use App\Modules\Auth\Services\AuthService;
use App\Modules\Dashboard\Services\DashboardRealtimeService;
use CodeIgniter\HTTP\ResponseInterface;
/**
* Realtime Stream Controller
*
* Server-Sent Events (SSE) for live attendance updates.
* Role-aware: stream content is filtered by current user's roles (ADMIN / WALI_KELAS / GURU_MAPEL / ORANG_TUA).
*/
class RealtimeStreamController extends BaseApiController
{
protected DashboardRealtimeService $realtimeService;
protected AuthService $authService;
/** Stream interval in seconds */
protected int $intervalSeconds = 2;
/** Max stream duration in seconds */
protected int $timeoutSeconds = 60;
public function __construct()
{
$this->realtimeService = new DashboardRealtimeService();
$this->authService = new AuthService();
}
/**
* GET /api/dashboard/stream
*
* SSE stream: every 2s check for new attendance, send event; timeout after 60s.
* Uses after_id for incremental feed. User context from session applied for role filtering.
*
* @return ResponseInterface
*/
public function index(): ResponseInterface
{
ignore_user_abort(true);
set_time_limit(0);
while (ob_get_level() > 0) {
ob_end_flush();
}
$this->response->setHeader('Content-Type', 'text/event-stream');
$this->response->setHeader('Cache-Control', 'no-cache');
$this->response->setHeader('Connection', 'keep-alive');
$this->response->setHeader('X-Accel-Buffering', 'no'); // nginx
$this->response->setBody('');
if (function_exists('apache_setenv')) {
@apache_setenv('no-gzip', '1');
}
@ini_set('output_buffering', 'off');
@ini_set('zlib.output_compression', false);
$this->response->sendHeaders();
$currentUser = $this->authService->currentUser();
$startTime = time();
$lastId = (int) ($this->request->getGet('after_id') ?? 0);
while (true) {
if (time() - $startTime >= $this->timeoutSeconds) {
$this->sendEvent('timeout', ['message' => 'Stream ended after ' . $this->timeoutSeconds . 's']);
break;
}
$rows = $this->realtimeService->getAttendanceSinceId($lastId, 50, $currentUser);
if (empty($rows)) {
$this->sendEvent('heartbeat', ['ts' => gmdate('Y-m-d\TH:i:s\Z')]);
} else {
foreach ($rows as $row) {
$this->sendEvent('attendance', $row);
$lastId = max($lastId, $row['id']);
}
}
flush();
sleep($this->intervalSeconds);
}
return $this->response;
}
/**
* Send one SSE event (event name + data line)
*
* @param string $event Event name
* @param array $data Data to send as JSON
*/
protected function sendEvent(string $event, array $data): void
{
echo 'event: ' . $event . "\n";
echo 'data: ' . json_encode($data, JSON_UNESCAPED_UNICODE) . "\n\n";
flush();
}
}