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'); } }