aybimyazilim/laravel-expo-notifications
最新稳定版本:v1.0.0
Composer 安装命令:
composer require aybimyazilim/laravel-expo-notifications
包简介
Laravel için Expo Push Notifications servisi - React Native Expo uygulamalarına bildirim gönderme paketi
README 文档
README
Bu paket, Laravel uygulamalarından Expo (React Native) uygulamalarına push notification gönderebilmenizi sağlar. Kapsamlı hata yakalama, detaylı loglama ve notification takibi içerir.
✨ Özellikler
- ✅ Expo Push Notifications API entegrasyonu
- ✅ Kapsamlı hata yakalama ve handling
- ✅ Detaylı notification logları
- ✅ Veritabanı tabanlı notification takibi
- ✅ Bulk notification gönderimi
- ✅ İstatistik ve raporlama
- ✅ Queue desteği ve retry mekanizması
- ✅ Token validasyonu
- ✅ Receipt status kontrolü
- ✅ Artisan komutları
🚀 Kurulum
composer require aybimyazilim/laravel-expo-notifications
Config Dosyasını Yayınlayın
php artisan vendor:publish --tag=expo-notifications-config
Migration'ları Yayınlayın ve Çalıştırın
php artisan vendor:publish --tag=expo-notifications-migrations php artisan migrate
⚙️ Konfigürasyon
.env dosyanıza aşağıdaki ayarları ekleyin:
# Expo Notification Ayarları EXPO_NOTIFICATION_TIMEOUT=30 EXPO_DEFAULT_SOUND=default EXPO_DEFAULT_PRIORITY=default EXPO_DEFAULT_CHANNEL_ID=default # Loglama Ayarları EXPO_ENABLE_LOGGING=true EXPO_LOG_REQUESTS=true EXPO_LOG_RESPONSES=true # Queue Ayarları EXPO_QUEUE_CONNECTION=default EXPO_QUEUE_NAME=notifications # Retry Ayarları EXPO_RETRY_ENABLED=true EXPO_RETRY_ATTEMPTS=3 EXPO_RETRY_DELAY=60 # Validasyon EXPO_VALIDATE_TOKENS=true EXPO_MAX_TITLE_LENGTH=100 EXPO_MAX_BODY_LENGTH=200 # Bulk Notification EXPO_BULK_LIMIT=100 EXPO_BATCH_SIZE=20
📱 Kullanım
Basit Notification Sınıfı
<?php namespace App\Notifications; use AybimYazilim\LaravelExpoNotifications\Notifications\ExpoNotification; class NewMessageNotification extends ExpoNotification { protected $message; public function __construct($message) { parent::__construct(); $this->message = $message; } public function toExpo($notifiable): array { return [ 'title' => 'Yeni Mesajınız Var! 💬', 'body' => "Gönderen: {$this->message->sender->name}", 'data' => [ 'type' => 'message', 'message_id' => $this->message->id, 'sender_id' => $this->message->sender_id, 'action' => 'open_chat', ], 'sound' => 'default', 'priority' => 'high', 'channelId' => 'messages', ]; } }
User Model Ayarları
<?php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use AybimYazilim\LaravelExpoNotifications\Models\ExpoNotificationLog; class User extends Authenticatable { // Expo token için routing public function routeNotificationForExpo() { return $this->expo_push_token; } // Notification geçmişi public function expoNotifications() { return $this->morphMany(ExpoNotificationLog::class, 'notifiable'); } // Son notification'ları al public function recentNotifications($limit = 10) { return $this->expoNotifications() ->latest() ->limit($limit) ->get(); } }
Notification Gönderimi
use App\Notifications\NewMessageNotification; use Illuminate\Support\Facades\Notification; // Kullanıcıya gönder $user = User::find(1); $user->notify(new NewMessageNotification($message)); // Birden fazla kullanıcıya gönder $users = User::whereNotNull('expo_push_token')->get(); Notification::send($users, new NewMessageNotification($message)); // Belirli bir token'a gönder Notification::route('expo', 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]') ->notify(new NewMessageNotification($message)); // Bulk gönderim $tokens = ['token1', 'token2', 'token3']; foreach ($tokens as $token) { Notification::route('expo', $token) ->notify(new NewMessageNotification($message)); }
Hata Yakalama
use AybimYazilim\LaravelExpoNotifications\Exceptions\ExpoNotificationException; use AybimYazilim\LaravelExpoNotifications\Exceptions\InvalidTokenException; try { $user->notify(new NewMessageNotification($message)); } catch (InvalidTokenException $e) { Log::error('Geçersiz Expo token: ' . $e->getMessage()); // Token'ı temizle veya güncelle $user->update(['expo_push_token' => null]); } catch (ExpoNotificationException $e) { Log::error('Expo notification hatası: ' . $e->getMessage()); }
📊 İstatistikler ve Loglama
Artisan Komutları
# Notification istatistiklerini görüntüle php artisan expo:stats 24h php artisan expo:stats 7d php artisan expo:stats 30d # Test notification gönder php artisan expo:test-notification "ExponentPushToken[xxx]" "Test Title" "Test Body"
Programatik İstatistikler
use AybimYazilim\LaravelExpoNotifications\Services\ExpoLogService; $logService = new ExpoLogService(); // Son 24 saatin istatistikleri $stats = $logService->getStats('24h'); /* [ 'total' => 250, 'sent' => 240, 'failed' => 10, 'pending' => 0, 'success_rate' => 96.0, 'period' => '24h' ] */ // Başarısız notification'ları al $failedNotifications = $logService->getFailedNotifications(50); // En çok kullanılan notification türleri $topTypes = $logService->getTopNotificationTypes(10, '30d'); // Günlük istatistikler (son 30 gün) $dailyStats = $logService->getDailyStats(30); // Hata analizi $errorAnalysis = $logService->getErrorAnalysis('7d');
🎯 Expo React Native Entegrasyonu
Expo uygulamanızda push token almak için:
import * as Notifications from 'expo-notifications'; import * as Device from 'expo-device'; import Constants from 'expo-constants'; import { useEffect, useRef, useState } from 'react'; import { Platform } from 'react-native'; // Notification handler'ı ayarla Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowAlert: true, shouldPlaySound: true, shouldSetBadge: false, }), }); export default function App() { const [expoPushToken, setExpoPushToken] = useState(''); const notificationListener = useRef(); const responseListener = useRef(); useEffect(() => { registerForPushNotificationsAsync().then(token => setExpoPushToken(token)); // Notification alındığında çalışır notificationListener.current = Notifications.addNotificationReceivedListener(notification => { console.log('Notification alındı:', notification); handleNotificationReceived(notification); }); // Notification'a tıklandığında çalışır responseListener.current = Notifications.addNotificationResponseReceivedListener(response => { console.log('Notification\'a tıklandı:', response); handleNotificationResponse(response); }); return () => { Notifications.removeNotificationSubscription(notificationListener.current); Notifications.removeNotificationSubscription(responseListener.current); }; }, []); return ( // Your app content ); } async function registerForPushNotificationsAsync() { let token; if (Platform.OS === 'android') { await Notifications.setNotificationChannelAsync('default', { name: 'default', importance: Notifications.AndroidImportance.MAX, vibrationPattern: [0, 250, 250, 250], lightColor: '#FF231F7C', }); // Özel kanallar oluştur await Notifications.setNotificationChannelAsync('messages', { name: 'Messages', importance: Notifications.AndroidImportance.HIGH, vibrationPattern: [0, 250, 250, 250], sound: 'message_sound.wav', }); await Notifications.setNotificationChannelAsync('orders', { name: 'Orders', importance: Notifications.AndroidImportance.HIGH, vibrationPattern: [0, 500, 250, 500], sound: 'order_sound.wav', }); await Notifications.setNotificationChannelAsync('promotions', { name: 'Promotions', importance: Notifications.AndroidImportance.DEFAULT, vibrationPattern: [0, 250], sound: 'promotion_sound.wav', }); } if (Device.isDevice) { const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus; if (existingStatus !== 'granted') { const { status } = await Notifications.requestPermissionsAsync(); finalStatus = status; } if (finalStatus !== 'granted') { console.log('Push notification izni alınamadı!'); return; } try { token = (await Notifications.getExpoPushTokenAsync({ projectId: Constants.expoConfig?.extra?.eas?.projectId, })).data; console.log('Expo Push Token:', token); // Token'ı Laravel backend'e gönder await sendTokenToBackend(token); } catch (error) { console.error('Token alma hatası:', error); } } else { console.log('Push notifications sadece fiziksel cihazlarda çalışır'); } return token; } async function sendTokenToBackend(token) { try { const response = await fetch('https://your-laravel-app.com/api/user/expo-token', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}`, 'Accept': 'application/json', }, body: JSON.stringify({ expo_push_token: token, device_info: { platform: Platform.OS, version: Platform.Version, brand: Device.brand, model: Device.modelName, } }), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); console.log('Token başarıyla gönderildi:', result); } catch (error) { console.error('Token gönderme hatası:', error); } } // Notification alındığında çağrılır function handleNotificationReceived(notification) { const { title, body, data } = notification.request.content; // Custom handling based on notification type switch (data?.type) { case 'message': // Update message count, show in-app notification etc. updateMessageCount(); break; case 'order_status': // Update order status, refresh order screen refreshOrderStatus(data.order_id); break; case 'promotion': // Show promotion banner, update promotions list showPromotionBanner(data.promotion_id); break; default: console.log('Unknown notification type:', data?.type); } } // Notification'a tıklandığında çağrılır function handleNotificationResponse(response) { const { data } = response.notification.request.content; // Navigate based on notification action switch (data?.action) { case 'open_chat': navigation.navigate('Chat', { messageId: data.message_id, senderId: data.sender_id }); break; case 'open_order': navigation.navigate('OrderDetail', { orderId: data.order_id }); break; case 'open_promotion': navigation.navigate('Promotion', { promotionId: data.promotion_id }); break; case 'open_profile': navigation.navigate('Profile'); break; default: navigation.navigate('Home'); } } // Helper functions function updateMessageCount() { // Update message count in your state management } function refreshOrderStatus(orderId) { // Refresh order status } function showPromotionBanner(promotionId) { // Show promotion banner }
🔧 Laravel API Endpoint'leri
// routes/api.php Route::middleware('auth:sanctum')->group(function () { Route::post('/user/expo-token', [UserController::class, 'updateExpoToken']); Route::get('/notifications/history', [NotificationController::class, 'history']); Route::post('/notifications/mark-as-read', [NotificationController::class, 'markAsRead']); }); // app/Http/Controllers/UserController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; class UserController extends Controller { public function updateExpoToken(Request $request): JsonResponse { $request->validate([ 'expo_push_token' => 'required|string', 'device_info' => 'nullable|array', ]); $user = $request->user(); $user->update([ 'expo_push_token' => $request->expo_push_token, 'device_info' => $request->device_info, 'token_updated_at' => now(), ]); return response()->json([ 'success' => true, 'message' => 'Expo token başarıyla güncellendi', ]); } } // app/Http/Controllers/NotificationController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; use AybimYazilim\LaravelExpoNotifications\Services\ExpoLogService; class NotificationController extends Controller { protected $expoLogService; public function __construct(ExpoLogService $expoLogService) { $this->expoLogService = $expoLogService; } public function history(Request $request): JsonResponse { $user = $request->user(); $history = $this->expoLogService->getUserNotificationHistory( get_class($user), $user->id, $request->get('limit', 50) ); return response()->json([ 'success' => true, 'data' => $history, ]); } public function markAsRead(Request $request): JsonResponse { $request->validate([ 'notification_ids' => 'required|array', 'notification_ids.*' => 'integer|exists:expo_notification_logs,id', ]); $user = $request->user(); ExpoNotificationLog::whereIn('id', $request->notification_ids) ->where('notifiable_type', get_class($user)) ->where('notifiable_id', $user->id) ->update(['read_at' => now()]); return response()->json([ 'success' => true, 'message' => 'Notification\'lar okundu olarak işaretlendi', ]); } }
📱 Advanced React Native Features
Notification Kategorileri ve İşlemler
// Notification kategorileri ve işlemleri tanımla import * as Notifications from 'expo-notifications'; // Kategorileri ayarla await Notifications.setNotificationCategoryAsync('message', [ { identifier: 'reply', buttonTitle: 'Yanıtla', textInput: { submitButtonTitle: 'Gönder', placeholder: 'Yanıtınızı yazın...', }, }, { identifier: 'mark_read', buttonTitle: 'Okundu', options: { opensAppToForeground: false, }, }, ]); await Notifications.setNotificationCategoryAsync('order', [ { identifier: 'view_order', buttonTitle: 'Siparişi Görüntüle', }, { identifier: 'track_order', buttonTitle: 'Takip Et', }, ]); // Action response handler responseListener.current = Notifications.addNotificationResponseReceivedListener(response => { const { actionIdentifier, userText } = response; const { data } = response.notification.request.content; switch (actionIdentifier) { case 'reply': handleReplyAction(data.message_id, userText); break; case 'mark_read': handleMarkAsReadAction(data.message_id); break; case 'view_order': navigation.navigate('OrderDetail', { orderId: data.order_id }); break; case 'track_order': navigation.navigate('OrderTracking', { orderId: data.order_id }); break; default: handleNotificationResponse(response); } }); async function handleReplyAction(messageId, replyText) { try { await fetch('https://your-laravel-app.com/api/messages/reply', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}`, }, body: JSON.stringify({ message_id: messageId, reply: replyText, }), }); } catch (error) { console.error('Reply gönderme hatası:', error); } }
Badge Yönetimi
import * as Notifications from 'expo-notifications'; // Badge sayısını ayarla async function setBadgeCount(count) { await Notifications.setBadgeCountAsync(count); } // Badge sayısını temizle async function clearBadge() { await Notifications.setBadgeCountAsync(0); } // Uygulama açıldığında badge'i temizle useEffect(() => { const subscription = AppState.addEventListener('change', (nextAppState) => { if (nextAppState === 'active') { clearBadge(); } }); return () => subscription?.remove(); }, []);
### Bulk Notification Gönderimi
```php
use AybimYazilim\LaravelExpoNotifications\Services\ExpoService;
$expoService = app(ExpoService::class);
$messages = [
[
'to' => 'ExponentPushToken[token1]',
'title' => 'Title 1',
'body' => 'Body 1',
],
[
'to' => 'ExponentPushToken[token2]',
'title' => 'Title 2',
'body' => 'Body 2',
],
// ... daha fazla mesaj
];
$response = $expoService->sendBulkNotifications($messages);
Custom Exception Handling
namespace App\Exceptions; use AybimYazilim\LaravelExpoNotifications\Exceptions\ExpoNotificationException; class Handler extends ExceptionHandler { public function register() { $this->reportable(function (ExpoNotificationException $e) { // Özel Expo notification hata loglama Log::channel('expo')->error('Expo Notification Error', [ 'message' => $e->getMessage(), 'context' => $e->getContext(), 'trace' => $e->getTraceAsString() ]); // Slack, email vb. bildirim gönder if (app()->environment('production')) { $this->notifyAdmins($e); } }); } }
🧪 Testing
use AybimYazilim\LaravelExpoNotifications\Tests\TestCase; use Illuminate\Support\Facades\Http; class ExpoNotificationTest extends TestCase { /** @test */ public function it_sends_expo_notification_successfully() { Http::fake([ 'exp.host/--/api/v2/push/send' => Http::response([ 'data' => [ [ 'status' => 'ok', 'id' => 'test-ticket-id' ] ] ]) ]); $user = User::factory()->create([ 'expo_push_token' => 'ExponentPushToken[test-token]' ]); $user->notify(new NewMessageNotification($message)); Http::assertSent(function ($request) { return str_contains($request->url(), 'exp.host') && $request['title'] === 'Yeni Mesajınız Var! 💬'; }); $this->assertDatabaseHas('expo_notification_logs', [ 'expo_token' => 'ExponentPushToken[test-token]', 'status' => 'sent' ]); } /** @test */ public function it_handles_invalid_token_error() { Http::fake([ 'exp.host/--/api/v2/push/send' => Http::response([ 'data' => [ [ 'status' => 'error', 'message' => 'DeviceNotRegistered', 'details' => [ 'error' => 'DeviceNotRegistered' ] ] ] ]) ]); $this->expectException(InvalidTokenException::class); $user = User::factory()->create([ 'expo_push_token' => 'invalid-token' ]); $user->notify(new NewMessageNotification($message)); } }
📋 Exception Türleri
ExpoNotificationException: Genel Expo notification hatalarıInvalidTokenException: Geçersiz veya kayıtlı olmayan tokenInvalidMessageException: Geçersiz mesaj formatı
🔍 Debugging
Debug modu için .env dosyanıza:
LOG_CHANNEL=stack EXPO_LOG_REQUESTS=true EXPO_LOG_RESPONSES=true
Log dosyalarında detaylı Expo notification gönderim bilgilerini görebilirsiniz.
📝 Changelog
v1.0.0
- ✅ Expo Push Notifications API entegrasyonu
- ✅ Kapsamlı hata yakalama sistemi
- ✅ Detaylı notification loglama
- ✅ Bulk notification desteği
- ✅ İstatistik ve raporlama
- ✅ Receipt status kontrolü
- ✅ Queue desteği ve retry mekanizması
🤝 Katkıda Bulunma
- Fork edin
- Feature branch oluşturun (
git checkout -b feature/amazing-feature) - Değişikliklerinizi commit edin (
git commit -m 'Add amazing feature') - Branch'i push edin (
git push origin feature/amazing-feature) - Pull Request oluşturun
📄 Lisans
Bu paket MIT lisansı altında yayınlanmıştır.
📞 Destek
Herhangi bir sorun yaşarsanız, GitHub Issues üzerinden bildirebilirsiniz.
统计信息
- 总下载量: 30
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-09-12