getServer(); if (!$server || !isset($server->wsTable)) { Log::warning('WebSocket server not available', ['user_id' => $userId]); return false; } $fdInfo = $server->wsTable->get('uid:' . $userId); if (!$fdInfo || !$fdInfo['value']) { Log::info('User not connected to WebSocket', ['user_id' => $userId]); return false; } $fd = (int)$fdInfo['value']; if (!$server->isEstablished($fd)) { Log::info('WebSocket connection not established', ['user_id' => $userId, 'fd' => $fd]); // Clean up stale connection $server->wsTable->del('uid:' . $userId); $server->wsTable->del('fd:' . $fd); return false; } $server->push($fd, json_encode($data)); Log::info('Message sent to user via WebSocket', [ 'user_id' => $userId, 'fd' => $fd, 'data' => $data ]); return true; } /** * Send message to multiple users * * @param array $userIds * @param array $data * @return array Array of user IDs who received the message */ public function sendToUsers(array $userIds, array $data): array { $sentTo = []; foreach ($userIds as $userId) { if ($this->sendToUser($userId, $data)) { $sentTo[] = $userId; } } return $sentTo; } /** * Broadcast message to all connected clients * * @param array $data * @param int|null $excludeUserId User ID to exclude from broadcast * @return int Number of clients the message was sent to */ public function broadcast(array $data, ?int $excludeUserId = null): int { $server = $this->getServer(); if (!$server) { Log::warning('WebSocket server not available for broadcast'); return 0; } $message = json_encode($data); $count = 0; foreach ($server->connections as $fd) { if (!$server->isEstablished($fd)) { continue; } // Check if we should exclude this user if ($excludeUserId) { $fdInfo = $server->wsTable->get('fd:' . $fd); if ($fdInfo && $fdInfo['value'] == $excludeUserId) { continue; } } $server->push($fd, $message); $count++; } Log::info('Broadcast sent via WebSocket', [ 'data' => $data, 'exclude_user_id' => $excludeUserId, 'count' => $count ]); return $count; } /** * Send message to all subscribers of a channel * * @param string $channel * @param array $data * @return int Number of subscribers who received the message */ public function sendToChannel(string $channel, array $data): int { $server = $this->getServer(); if (!$server || !isset($server->wsTable)) { Log::warning('WebSocket server not available for channel broadcast', ['channel' => $channel]); return 0; } $count = 0; $message = json_encode($data); // Iterate through all connections and check if they're subscribed to the channel foreach ($server->connections as $fd) { if (!$server->isEstablished($fd)) { continue; } $subscription = $server->wsTable->get('channel:' . $channel . ':fd:' . $fd); if ($subscription) { $server->push($fd, $message); $count++; } } Log::info('Channel message sent via WebSocket', [ 'channel' => $channel, 'data' => $data, 'count' => $count ]); return $count; } /** * Get online user count * * @return int */ public function getOnlineUserCount(): int { $server = $this->getServer(); if (!$server || !isset($server->wsTable)) { return 0; } // Count established connections $count = 0; foreach ($server->connections as $fd) { if ($server->isEstablished($fd)) { $count++; } } return $count; } /** * Check if a user is online * * @param int $userId * @return bool */ public function isUserOnline(int $userId): bool { $server = $this->getServer(); if (!$server || !isset($server->wsTable)) { return false; } $fdInfo = $server->wsTable->get('uid:' . $userId); if (!$fdInfo || !$fdInfo['value']) { return false; } $fd = (int)$fdInfo['value']; return $server->isEstablished($fd); } /** * Disconnect a user from WebSocket * * @param int $userId * @return bool */ public function disconnectUser(int $userId): bool { $server = $this->getServer(); if (!$server || !isset($server->wsTable)) { return false; } $fdInfo = $server->wsTable->get('uid:' . $userId); if (!$fdInfo || !$fdInfo['value']) { return false; } $fd = (int)$fdInfo['value']; if ($server->isEstablished($fd)) { $server->push($fd, json_encode([ 'type' => 'disconnect', 'data' => [ 'message' => 'You have been disconnected', 'timestamp' => time() ] ])); // Close the connection $server->disconnect($fd); // Clean up $server->wsTable->del('uid:' . $userId); $server->wsTable->del('fd:' . $fd); Log::info('User disconnected from WebSocket by server', [ 'user_id' => $userId, 'fd' => $fd ]); return true; } return false; } /** * Get all online user IDs * * @return array */ public function getOnlineUserIds(): array { $server = $this->getServer(); if (!$server || !isset($server->wsTable)) { return []; } $userIds = []; foreach ($server->connections as $fd) { if (!$server->isEstablished($fd)) { continue; } $fdInfo = $server->wsTable->get('fd:' . $fd); if ($fdInfo && $fdInfo['value']) { $userIds[] = (int)$fdInfo['value']; } } return array_unique($userIds); } /** * Send system notification to all online users * * @param string $title * @param string $message * @param string $type * @param array $extraData * @return int */ public function sendSystemNotification(string $title, string $message, string $type = 'info', array $extraData = []): int { $data = [ 'type' => 'notification', 'data' => [ 'title' => $title, 'message' => $message, 'type' => $type, // info, success, warning, error 'timestamp' => time(), ...$extraData ] ]; return $this->broadcast($data); } /** * Send notification to specific users * * @param array $userIds * @param string $title * @param string $message * @param string $type * @param array $extraData * @return array */ public function sendNotificationToUsers(array $userIds, string $title, string $message, string $type = 'info', array $extraData = []): array { $data = [ 'type' => 'notification', 'data' => [ 'title' => $title, 'message' => $message, 'type' => $type, 'timestamp' => time(), ...$extraData ] ]; return $this->sendToUsers($userIds, $data); } /** * Push data update to specific users * * @param array $userIds * @param string $resourceType * @param string $action * @param array $data * @return array */ public function pushDataUpdate(array $userIds, string $resourceType, string $action, array $data): array { $message = [ 'type' => 'data_update', 'data' => [ 'resource_type' => $resourceType, // e.g., 'user', 'order', 'product' 'action' => $action, // create, update, delete 'data' => $data, 'timestamp' => time() ] ]; return $this->sendToUsers($userIds, $message); } /** * Push data update to a channel * * @param string $channel * @param string $resourceType * @param string $action * @param array $data * @return int */ public function pushDataUpdateToChannel(string $channel, string $resourceType, string $action, array $data): int { $message = [ 'type' => 'data_update', 'data' => [ 'resource_type' => $resourceType, 'action' => $action, 'data' => $data, 'timestamp' => time() ] ]; return $this->sendToChannel($channel, $message); } }