Initial commit

This commit is contained in:
Ronald Huynen
2026-03-23 21:37:59 +01:00
commit 2547717edb
2193 changed files with 972171 additions and 0 deletions

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