File "FcmTokenService.php"
Full Path: /home/trinadezambia/public_html/admin_panel/app/Services/FcmTokenService.php
File size: 6.4 KB
MIME-type: text/x-php
Charset: utf-8
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\FcmToken;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
final class FcmTokenService
{
/**
* Store or update FCM token for a user and device.
*
* @param User $user
* @param string $fcmToken
* @param string $deviceType
* @param string|null $deviceId
* @return FcmToken
*/
public function storeOrUpdateToken(
User $user,
string $fcmToken,
string $deviceType = 'android',
?string $deviceId = null
): FcmToken {
// Validate device type
$deviceType = $this->normalizeDeviceType($deviceType);
// Check if token already exists
$existingToken = FcmToken::where('fcm_token', $fcmToken)->first();
if ($existingToken) {
// If token belongs to different user, update it
if ($existingToken->user_id !== $user->id) {
$existingToken->user_id = $user->id;
$existingToken->device_type = $deviceType;
$existingToken->device_id = $deviceId;
$existingToken->last_used_at = now();
$existingToken->save();
return $existingToken;
}
// If token belongs to same user, just update last_used_at
$existingToken->touchLastUsed();
if ($existingToken->device_type !== $deviceType || $existingToken->device_id !== $deviceId) {
$existingToken->device_type = $deviceType;
$existingToken->device_id = $deviceId;
$existingToken->save();
}
return $existingToken;
}
// Check if user already has a token for this device_id (if provided)
if ($deviceId) {
$deviceToken = FcmToken::forUser($user->id)
->where('device_id', $deviceId)
->where('device_type', $deviceType)
->first();
if ($deviceToken) {
// Update existing device token
$deviceToken->fcm_token = $fcmToken;
$deviceToken->last_used_at = now();
$deviceToken->save();
return $deviceToken;
}
}
// Create new token
return FcmToken::create([
'user_id' => $user->id,
'fcm_token' => $fcmToken,
'device_type' => $deviceType,
'device_id' => $deviceId,
'last_used_at' => now(),
]);
}
/**
* Remove or deactivate FCM token for a specific device.
*
* @param User $user
* @param string|null $fcmToken
* @param string|null $deviceId
* @return bool
*/
public function removeToken(User $user, ?string $fcmToken = null, ?string $deviceId = null): bool
{
$query = FcmToken::forUser($user->id);
if ($fcmToken) {
$query->where('fcm_token', $fcmToken);
}
if ($deviceId) {
$query->where('device_id', $deviceId);
}
$tokens = $query->get();
if ($tokens->isEmpty()) {
return false;
}
foreach ($tokens as $token) {
$token->delete();
}
return true;
}
/**
* Get all active FCM tokens for a user.
*
* @param User $user
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getUserTokens(User $user)
{
return FcmToken::forUser($user->id)
->with('user')
->get();
}
/**
* Get all active FCM tokens for multiple users.
*
* @param array<int> $userIds
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getUsersTokens(array $userIds)
{
return FcmToken::forUsers($userIds)
->with('user')
->get();
}
/**
* Get tokens grouped by device type for a user.
*
* @param User $user
* @return array<string, \Illuminate\Database\Eloquent\Collection>
*/
public function getUserTokensByDeviceType(User $user): array
{
$tokens = $this->getUserTokens($user);
return [
'android' => $tokens->where('device_type', 'android'),
'ios' => $tokens->where('device_type', 'ios'),
'web' => $tokens->where('device_type', 'web'),
];
}
/**
* Clean up invalid or expired tokens.
* This should be called when FCM returns an error for a token.
*
* @param string $fcmToken
* @return bool
*/
public function removeInvalidToken(string $fcmToken): bool
{
$token = FcmToken::where('fcm_token', $fcmToken)->first();
if ($token) {
Log::warning('Removing invalid FCM token', [
'token_id' => $token->id,
'user_id' => $token->user_id,
'device_type' => $token->device_type,
]);
return $token->delete();
}
return false;
}
/**
* Normalize device type to valid enum value.
*
* @param string $deviceType
* @return string
*/
private function normalizeDeviceType(string $deviceType): string
{
$deviceType = strtolower(trim($deviceType));
// Map common variations
$mapping = [
'mobile' => 'android',
'iphone' => 'ios',
'ipad' => 'ios',
'desktop' => 'web',
'browser' => 'web',
];
if (isset($mapping[$deviceType])) {
return $mapping[$deviceType];
}
// Validate against allowed values
if (in_array($deviceType, ['android', 'ios', 'web'], true)) {
return $deviceType;
}
// Default to android if invalid
return 'android';
}
/**
* Migrate existing tokens from users table (for backward compatibility).
*
* @param User $user
* @return void
*/
public function migrateUserTokens(User $user): void
{
// Migrate fcm_id (mobile token)
if ($user->fcm_id && !empty(trim($user->fcm_id))) {
$this->storeOrUpdateToken($user, trim($user->fcm_id), 'android');
}
// Migrate web_fcm (web token) if column exists
if (isset($user->web_fcm) && $user->web_fcm && !empty(trim($user->web_fcm))) {
$this->storeOrUpdateToken($user, trim($user->web_fcm), 'web');
}
}
}