Initial commit
This commit is contained in:
331
app/Http/Livewire/WireChat/TypingIndicator.php
Normal file
331
app/Http/Livewire/WireChat/TypingIndicator.php
Normal file
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
|
||||
// app/Http/Livewire/WireChat/TypingIndicator.php
|
||||
namespace App\Http\Livewire\WireChat;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Livewire\Component;
|
||||
|
||||
class TypingIndicator extends Component
|
||||
{
|
||||
// Public properties
|
||||
public $conversationId;
|
||||
public $typingUsers = [];
|
||||
public $currentUserId;
|
||||
public $currentUserName;
|
||||
public $currentUserType;
|
||||
public $currentUserAvatar;
|
||||
public $showAvatars = true;
|
||||
public $maxDisplay = 3;
|
||||
|
||||
protected $typingTimeout = 4;
|
||||
|
||||
public function mount($conversationId, $showAvatars = true, $maxDisplay = 3)
|
||||
{
|
||||
$this->conversationId = $conversationId;
|
||||
$this->showAvatars = $showAvatars;
|
||||
$this->maxDisplay = $maxDisplay;
|
||||
$this->typingUsers = [];
|
||||
|
||||
$user = auth()->user();
|
||||
if ($user) {
|
||||
$this->currentUserId = $user->id;
|
||||
$this->currentUserName = $user->name;
|
||||
$this->currentUserType = class_basename($user->getMorphClass());
|
||||
$this->currentUserAvatar = ($user->getMorphClass())::find($this->currentUserId)->profile_photo_path ?? null;
|
||||
}
|
||||
|
||||
$this->loadTypingUsers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key for this user's typing status
|
||||
*/
|
||||
private function getCacheKey()
|
||||
{
|
||||
// Simplified cache key format without special characters
|
||||
return "wirechat_typing_{$this->conversationId}_{$this->currentUserType}_{$this->currentUserId}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pattern for finding all typing users in this conversation
|
||||
*/
|
||||
private function getCachePattern()
|
||||
{
|
||||
return "wirechat_typing_{$this->conversationId}_*";
|
||||
}
|
||||
|
||||
/**
|
||||
* Start typing - called from JavaScript
|
||||
*/
|
||||
public function startTyping()
|
||||
{
|
||||
if (!$this->currentUserId) {
|
||||
\Log::warning('TypingIndicator: No current user ID');
|
||||
return;
|
||||
}
|
||||
|
||||
$cacheKey = $this->getCacheKey();
|
||||
|
||||
$userData = [
|
||||
'user_id' => $this->currentUserId,
|
||||
'user_name' => $this->currentUserName,
|
||||
'user_type' => $this->currentUserType,
|
||||
'avatar' => $this->currentUserAvatar,
|
||||
'started_at' => now(),
|
||||
'conversation_id' => $this->conversationId,
|
||||
];
|
||||
|
||||
try {
|
||||
// Store with expiration
|
||||
Cache::put($cacheKey, $userData, now()->addSeconds($this->typingTimeout + 2));
|
||||
|
||||
// Immediately load typing users to update the display
|
||||
$this->loadTypingUsers();
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('TypingIndicator: Error starting typing', [
|
||||
'error' => $e->getMessage(),
|
||||
'cache_key' => $cacheKey
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop typing - called from JavaScript
|
||||
*/
|
||||
public function stopTyping()
|
||||
{
|
||||
if (!$this->currentUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cacheKey = $this->getCacheKey();
|
||||
|
||||
try {
|
||||
Cache::forget($cacheKey);
|
||||
|
||||
$this->loadTypingUsers();
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('TypingIndicator: Error stopping typing', [
|
||||
'error' => $e->getMessage(),
|
||||
'cache_key' => $cacheKey
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load typing users from cache
|
||||
*/
|
||||
public function loadTypingUsers()
|
||||
{
|
||||
try {
|
||||
$typingUsers = [];
|
||||
|
||||
// Get the correct Redis connection for cache
|
||||
$cacheRedisConnection = config('cache.stores.redis.connection', 'default');
|
||||
|
||||
// From the exact debugging, the actual format is:
|
||||
// Redis key: timebankcc_dev_on_thinkpad_p53_database_timebankcc_dev_on_thinkpad_p53_cache:wirechat_typing_1_User_161
|
||||
// But we search with: timebankcc_dev_on_thinkpad_p53_cache:wirechat_typing_1_*
|
||||
// And need to extract: wirechat_typing_1_User_161
|
||||
|
||||
$cachePrefix = config('cache.prefix', '');
|
||||
$databasePrefix = config('database.redis.options.prefix', '');
|
||||
$pattern = $this->getCachePattern();
|
||||
|
||||
// Use the working search pattern (cache prefix only)
|
||||
$searchPattern = $cachePrefix . ':' . $pattern;
|
||||
|
||||
// But the actual key prefix includes database prefix
|
||||
$actualKeyPrefix = $databasePrefix . $cachePrefix . ':';
|
||||
|
||||
if (config('cache.default') === 'redis') {
|
||||
// Use the CACHE Redis connection (database 1)
|
||||
$connection = Redis::connection($cacheRedisConnection);
|
||||
$redisKeys = $connection->keys($searchPattern);
|
||||
|
||||
foreach ($redisKeys as $redisKey) {
|
||||
// Extract the Laravel cache key by removing the ACTUAL prefix (database + cache)
|
||||
if (strpos($redisKey, $actualKeyPrefix) === 0) {
|
||||
$laravelCacheKey = substr($redisKey, strlen($actualKeyPrefix));
|
||||
} else {
|
||||
// Fallback - the key format might be different
|
||||
\Log::warning('TypingIndicator: Unexpected key format', [
|
||||
'redis_key' => $redisKey,
|
||||
'expected_prefix' => $actualKeyPrefix
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use Laravel Cache to get the data (it handles serialization properly)
|
||||
$userData = Cache::get($laravelCacheKey);
|
||||
|
||||
if ($userData && isset($userData['user_id']) && $userData['user_id'] != $this->currentUserId) {
|
||||
if (isset($userData['started_at']) && $userData['started_at']->diffInSeconds(now()) <= $this->typingTimeout) {
|
||||
$typingUsers[] = $userData;
|
||||
\Log::info('TypingIndicator: Added typing user', [
|
||||
'user_id' => $userData['user_id'],
|
||||
'user_name' => $userData['user_name']
|
||||
]);
|
||||
} else {
|
||||
// Clean up expired entries
|
||||
Cache::forget($laravelCacheKey);
|
||||
}
|
||||
} elseif ($userData && $userData['user_id'] == $this->currentUserId) {
|
||||
\Log::info('TypingIndicator: Ignoring current user\'s typing', [
|
||||
'current_user_id' => $this->currentUserId
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback for non-Redis cache drivers
|
||||
$registryKey = "typing_registry_{$this->conversationId}";
|
||||
$registry = Cache::get($registryKey, []);
|
||||
|
||||
foreach ($registry as $key) {
|
||||
$userData = Cache::get($key);
|
||||
if ($userData && isset($userData['user_id']) && $userData['user_id'] != $this->currentUserId) {
|
||||
if (isset($userData['started_at']) && $userData['started_at']->diffInSeconds(now()) <= $this->typingTimeout) {
|
||||
$typingUsers[] = $userData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->typingUsers = collect($typingUsers)->take($this->maxDisplay)->toArray();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->typingUsers = [];
|
||||
\Log::error('TypingIndicator: Error loading typing users', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get typing text for display
|
||||
*/
|
||||
public function getTypingText()
|
||||
{
|
||||
$count = is_array($this->typingUsers) ? count($this->typingUsers) : 0;
|
||||
|
||||
if ($count === 0) {
|
||||
return '';
|
||||
} elseif ($count === 1) {
|
||||
return ($this->typingUsers[0]['user_name'] ?? 'Someone') . ' is typing...';
|
||||
} elseif ($count === 2) {
|
||||
$name1 = $this->typingUsers[0]['user_name'] ?? 'Someone';
|
||||
$name2 = $this->typingUsers[1]['user_name'] ?? 'Someone';
|
||||
return $name1 . ' and ' . $name2 . ' are typing...';
|
||||
} else {
|
||||
$name1 = $this->typingUsers[0]['user_name'] ?? 'Someone';
|
||||
return $name1 . ' and ' . ($count - 1) . ' others are typing...';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manual refresh method
|
||||
*/
|
||||
public function refresh()
|
||||
{
|
||||
$this->loadTypingUsers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced debug method
|
||||
*/
|
||||
public function debug()
|
||||
{
|
||||
$cacheKey = $this->getCacheKey();
|
||||
$pattern = $this->getCachePattern();
|
||||
|
||||
// Get Redis connection info
|
||||
$cacheRedisConnection = config('cache.stores.redis.connection', 'default');
|
||||
$cachePrefix = config('cache.prefix', '');
|
||||
$databasePrefix = config('database.redis.options.prefix', '');
|
||||
|
||||
// Build the full pattern accounting for both prefixes
|
||||
$fullPattern = '';
|
||||
if ($databasePrefix) {
|
||||
$fullPattern .= $databasePrefix;
|
||||
}
|
||||
if ($cachePrefix) {
|
||||
$fullPattern .= $cachePrefix . ':';
|
||||
}
|
||||
$fullPattern .= $pattern;
|
||||
|
||||
$redisKeys = [];
|
||||
$redisData = [];
|
||||
|
||||
if (config('cache.default') === 'redis') {
|
||||
try {
|
||||
$connection = Redis::connection($cacheRedisConnection);
|
||||
|
||||
// Check for typing keys with the full pattern
|
||||
$allTypingKeys = $connection->keys($databasePrefix ? $databasePrefix . $cachePrefix . ':*typing*' : $cachePrefix . ':*typing*');
|
||||
$patternKeys = $connection->keys($fullPattern);
|
||||
|
||||
$redisKeys = [
|
||||
'all_typing_keys' => $allTypingKeys,
|
||||
'pattern_keys' => $patternKeys,
|
||||
'current_user_key' => $cacheKey,
|
||||
'full_pattern_searched' => $fullPattern,
|
||||
'cache_redis_connection' => $cacheRedisConnection
|
||||
];
|
||||
|
||||
foreach ($patternKeys as $key) {
|
||||
// Extract the Laravel cache key by removing both prefixes
|
||||
$cleanKey = $key;
|
||||
if ($databasePrefix && strpos($cleanKey, $databasePrefix) === 0) {
|
||||
$cleanKey = substr($cleanKey, strlen($databasePrefix));
|
||||
}
|
||||
if ($cachePrefix && strpos($cleanKey, $cachePrefix . ':') === 0) {
|
||||
$cleanKey = substr($cleanKey, strlen($cachePrefix . ':'));
|
||||
}
|
||||
|
||||
$data = Cache::get($cleanKey);
|
||||
if ($data) {
|
||||
$redisData[] = [
|
||||
'full_redis_key' => $key,
|
||||
'clean_cache_key' => $cleanKey,
|
||||
'data' => $data
|
||||
];
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('TypingIndicator: Redis debug error', ['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
$debugInfo = [
|
||||
'conversationId' => $this->conversationId,
|
||||
'typingUsers' => $this->typingUsers,
|
||||
'typingUsersCount' => is_array($this->typingUsers) ? count($this->typingUsers) : 'not-array',
|
||||
'currentUserId' => $this->currentUserId,
|
||||
'currentUserName' => $this->currentUserName,
|
||||
'currentUserType' => $this->currentUserType,
|
||||
'showAvatars' => $this->showAvatars,
|
||||
'cacheDriver' => config('cache.default'),
|
||||
'cacheKey' => $cacheKey,
|
||||
'cachePattern' => $pattern,
|
||||
'cachePrefix' => $cachePrefix,
|
||||
'databasePrefix' => $databasePrefix,
|
||||
'fullPattern' => $fullPattern,
|
||||
'cacheRedisConnection' => $cacheRedisConnection,
|
||||
'redisKeys' => $redisKeys,
|
||||
'redisData' => $redisData,
|
||||
'typingTimeout' => $this->typingTimeout,
|
||||
];
|
||||
|
||||
\Log::info('TypingIndicator: Debug info', $debugInfo);
|
||||
|
||||
return $debugInfo;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.wire-chat.typing-indicator');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user