feat: Add iOS push notification support (iOS 16.4+)
- Platform detection utilities (is_ios, is_standalone, supports_push) - iOS-specific meta tags and Apple touch icons - Auto-detect iOS and show user-friendly message when not in standalone mode - Enhanced Service Worker with iOS-compatible notification options - Comprehensive iOS push notification documentation - manifest.json scope for PWA compliance - Only works when added to Home Screen (iOS Safari limitation)
This commit is contained in:
113
docs/IOS_PUSH_NOTIFICATIONS.md
Normal file
113
docs/IOS_PUSH_NOTIFICATIONS.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# iOS Push Notification Desteği
|
||||||
|
|
||||||
|
VibeTorrent artık iOS 16.4+ cihazlarda push notification destekliyor! 🎉
|
||||||
|
|
||||||
|
## Gereksinimler
|
||||||
|
|
||||||
|
- **iOS 16.4 veya üzeri** (Mart 2023 ve sonrası)
|
||||||
|
- Safari tarayıcısı
|
||||||
|
- HTTPS bağlantısı (production ortamında)
|
||||||
|
|
||||||
|
## Nasıl Kullanılır?
|
||||||
|
|
||||||
|
### 1. Ana Ekrana Ekle
|
||||||
|
|
||||||
|
Push notification'lar **sadece Home Screen'e eklenmiş PWA'larda çalışır**. Safari'den doğrudan çalışmaz!
|
||||||
|
|
||||||
|
#### Adımlar:
|
||||||
|
1. Safari'de uygulamayı açın
|
||||||
|
2. Alt kısımdaki **Paylaş** butonuna tıklayın (⬆️ ikonu)
|
||||||
|
3. **"Ana Ekrana Ekle"** seçeneğini seçin
|
||||||
|
4. İsmi onaylayıp **"Ekle"** butonuna basın
|
||||||
|
5. Ana ekrandaki ikona tıklayarak uygulamayı açın
|
||||||
|
|
||||||
|
### 2. Notification İzni Verin
|
||||||
|
|
||||||
|
İlk açılışta uygulama notification izni isteyecektir:
|
||||||
|
- **"İzin Ver"** seçeneğini seçin
|
||||||
|
- Eğer atlarsanız, daha sonra Safari ayarlarından izin verebilirsiniz
|
||||||
|
|
||||||
|
### 3. Push Notification Otomatik Aktif Olur
|
||||||
|
|
||||||
|
Ana ekrandan açılan PWA'da:
|
||||||
|
- Uygulama otomatik olarak push notification'a abone olur
|
||||||
|
- Torrent tamamlandığında bildirim alırsınız
|
||||||
|
- **Uygulama kapalı olsa bile bildirim gelir!**
|
||||||
|
|
||||||
|
## Teknik Detaylar
|
||||||
|
|
||||||
|
### iOS Safari Kısıtlamaları:
|
||||||
|
|
||||||
|
✅ **Çalışır:**
|
||||||
|
- Home Screen'e eklenmiş PWA
|
||||||
|
- iOS 16.4+ Safari
|
||||||
|
|
||||||
|
❌ **Çalışmaz:**
|
||||||
|
- Safari browser mode (standalone olmayan)
|
||||||
|
- iOS 16.4 altı sürümler
|
||||||
|
- Chrome veya diğer tarayıcılar iOS'ta (WebKit kısıtlaması)
|
||||||
|
|
||||||
|
### Test Etme:
|
||||||
|
|
||||||
|
1. iOS cihazınızdan production URL'e gidin (HTTPS gerekli)
|
||||||
|
2. Ana ekrana ekleyin
|
||||||
|
3. Bir torrent indirin ve tamamlanmasını bekleyin
|
||||||
|
4. Uygulamayı kapatın
|
||||||
|
5. Torrent tamamlandığında notification alacaksınız!
|
||||||
|
|
||||||
|
### Sorun Giderme:
|
||||||
|
|
||||||
|
**"Push notification desteklenmiyor" mesajı görüyorum:**
|
||||||
|
- Ana ekrana eklediniz mi? Safari'den değil, ana ekrandaki ikondan açmalısınız
|
||||||
|
- iOS 16.4+ sürümü mü kullanıyorsunuz?
|
||||||
|
|
||||||
|
**Notification gelmiyor:**
|
||||||
|
- Settings → VibeTorrent → Notifications → izinlerin açık olduğundan emin olun
|
||||||
|
- Ana ekrandaki PWA'dan açtığınızdan emin olun (Safari'den değil)
|
||||||
|
- Developer Console'da "Push subscription" log'unu kontrol edin
|
||||||
|
|
||||||
|
**Notification izni reddettim, nasıl yeniden açarım?**
|
||||||
|
- Settings → Safari → Advanced → Website Data → VibeTorrent'i silin
|
||||||
|
- Uygulamayı ana ekrandan silin ve yeniden ekleyin
|
||||||
|
|
||||||
|
## Platform Karşılaştırması
|
||||||
|
|
||||||
|
| Platform | Push Support | Gereksinim |
|
||||||
|
|----------|--------------|------------|
|
||||||
|
| **Android Chrome** | ✅ Tam destek | Browser veya PWA |
|
||||||
|
| **iOS 16.4+ Safari** | ✅ PWA destekli | Ana ekrana eklenmiş olmalı |
|
||||||
|
| **iOS 16.3 ve altı** | ❌ Desteklenmez | - |
|
||||||
|
| **Desktop (Chrome/Edge)** | ✅ Tam destek | Browser veya PWA |
|
||||||
|
| **Desktop Safari** | ⚠️ Sınırlı | macOS 13+ (Ventura) |
|
||||||
|
|
||||||
|
## Güvenlik
|
||||||
|
|
||||||
|
- VAPID anahtarları kullanılıyor
|
||||||
|
- End-to-end şifreli push notification
|
||||||
|
- Subscription'lar backend'de güvenli saklanıyor
|
||||||
|
- iOS Safari security model tam uyumlu
|
||||||
|
|
||||||
|
## Geliştiriciler İçin
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// iOS detection
|
||||||
|
if crate::utils::platform::is_ios() {
|
||||||
|
// iOS-specific kod
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standalone mode kontrolü
|
||||||
|
if crate::utils::platform::is_standalone() {
|
||||||
|
// PWA mode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push notification desteği
|
||||||
|
if crate::utils::platform::supports_push_notifications() {
|
||||||
|
// Subscribe
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kaynaklar
|
||||||
|
|
||||||
|
- [iOS Safari Web Push](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/)
|
||||||
|
- [PWA on iOS Guide](https://developer.apple.com/wwdc23/10120)
|
||||||
|
- [Web Push API](https://developer.mozilla.org/en-US/docs/Web/API/Push_API)
|
||||||
@@ -11,9 +11,13 @@
|
|||||||
<!-- PWA & Mobile Capable -->
|
<!-- PWA & Mobile Capable -->
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="VibeTorrent" />
|
||||||
<meta name="theme-color" content="#111827" />
|
<meta name="theme-color" content="#111827" />
|
||||||
<link rel="manifest" href="manifest.json" />
|
<link rel="manifest" href="manifest.json" />
|
||||||
<link rel="apple-touch-icon" href="icon-192.png" />
|
<link rel="apple-touch-icon" href="icon-192.png" />
|
||||||
|
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
|
||||||
|
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
|
||||||
|
|
||||||
<!-- Trunk Assets -->
|
<!-- Trunk Assets -->
|
||||||
<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="0" />
|
<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="0" />
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"short_name": "VibeTorrent",
|
"short_name": "VibeTorrent",
|
||||||
"description": "Modern web-based torrent client with real-time updates",
|
"description": "Modern web-based torrent client with real-time updates",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
|
"scope": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#1d232a",
|
"background_color": "#1d232a",
|
||||||
"theme_color": "#1d232a",
|
"theme_color": "#1d232a",
|
||||||
|
|||||||
@@ -16,6 +16,27 @@ pub fn App() -> impl IntoView {
|
|||||||
// Wait a bit for service worker to be ready
|
// Wait a bit for service worker to be ready
|
||||||
gloo_timers::future::TimeoutFuture::new(2000).await;
|
gloo_timers::future::TimeoutFuture::new(2000).await;
|
||||||
|
|
||||||
|
// Check if running on iOS and not standalone
|
||||||
|
if let Some(ios_message) = crate::utils::platform::get_ios_notification_info() {
|
||||||
|
tracing::warn!("iOS detected: {}", ios_message);
|
||||||
|
|
||||||
|
// Show toast to inform user
|
||||||
|
if let Some(store) = use_context::<crate::store::TorrentStore>() {
|
||||||
|
crate::store::show_toast_with_signal(
|
||||||
|
store.notifications,
|
||||||
|
shared::NotificationLevel::Info,
|
||||||
|
ios_message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if push notifications are supported
|
||||||
|
if !crate::utils::platform::supports_push_notifications() {
|
||||||
|
tracing::warn!("Push notifications not supported on this platform");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if Notification API is available and permission is granted
|
// Check if Notification API is available and permission is granted
|
||||||
let window = web_sys::window().expect("window should exist");
|
let window = web_sys::window().expect("window should exist");
|
||||||
if let Ok(notification_class) = js_sys::Reflect::get(&window, &"Notification".into()) {
|
if let Ok(notification_class) = js_sys::Reflect::get(&window, &"Notification".into()) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use tailwind_fuse::merge::tw_merge;
|
use tailwind_fuse::merge::tw_merge;
|
||||||
|
|
||||||
pub mod notification;
|
pub mod notification;
|
||||||
|
pub mod platform;
|
||||||
|
|
||||||
pub fn cn(classes: impl AsRef<str>) -> String {
|
pub fn cn(classes: impl AsRef<str>) -> String {
|
||||||
tw_merge(classes.as_ref())
|
tw_merge(classes.as_ref())
|
||||||
|
|||||||
70
frontend/src/utils/platform.rs
Normal file
70
frontend/src/utils/platform.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/// Platform detection utilities
|
||||||
|
|
||||||
|
/// Check if running on iOS
|
||||||
|
pub fn is_ios() -> bool {
|
||||||
|
let window = web_sys::window().expect("window should exist");
|
||||||
|
let navigator = window.navigator();
|
||||||
|
let user_agent = navigator.user_agent().unwrap_or_default();
|
||||||
|
|
||||||
|
user_agent.contains("iPhone")
|
||||||
|
|| user_agent.contains("iPad")
|
||||||
|
|| user_agent.contains("iPod")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if running as a standalone PWA (installed on home screen)
|
||||||
|
pub fn is_standalone() -> bool {
|
||||||
|
let window = web_sys::window().expect("window should exist");
|
||||||
|
let navigator = window.navigator();
|
||||||
|
|
||||||
|
// Check for iOS standalone mode
|
||||||
|
if let Ok(standalone) = js_sys::Reflect::get(&navigator, &"standalone".into()) {
|
||||||
|
if let Some(is_standalone) = standalone.as_bool() {
|
||||||
|
return is_standalone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check display-mode via matchMedia
|
||||||
|
if let Ok(match_media_fn) = js_sys::Reflect::get(&window, &"matchMedia".into()) {
|
||||||
|
if match_media_fn.is_function() {
|
||||||
|
let match_media = js_sys::Function::from(match_media_fn);
|
||||||
|
if let Ok(result) = match_media.call1(&window, &"(display-mode: standalone)".into()) {
|
||||||
|
if let Ok(matches) = js_sys::Reflect::get(&result, &"matches".into()) {
|
||||||
|
if let Some(is_match) = matches.as_bool() {
|
||||||
|
return is_match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if push notifications are supported
|
||||||
|
pub fn supports_push_notifications() -> bool {
|
||||||
|
let window = web_sys::window().expect("window should exist");
|
||||||
|
|
||||||
|
// Check if PushManager exists
|
||||||
|
if let Ok(navigator) = js_sys::Reflect::get(&window, &"navigator".into()) {
|
||||||
|
if let Ok(service_worker) = js_sys::Reflect::get(&navigator, &"serviceWorker".into()) {
|
||||||
|
if let Ok(push_manager) = js_sys::Reflect::get(&service_worker, &"PushManager".into()) {
|
||||||
|
return !push_manager.is_undefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get platform-specific notification message
|
||||||
|
pub fn get_ios_notification_info() -> Option<String> {
|
||||||
|
if is_ios() && !is_standalone() {
|
||||||
|
Some(
|
||||||
|
"iOS'ta push notification alabilmek için uygulamayı Ana Ekran'a eklemelisiniz. \
|
||||||
|
Safari menüsünden 'Ana Ekrana Ekle' seçeneğini kullanın."
|
||||||
|
.to_string()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -110,6 +110,13 @@ self.addEventListener('push', (event) => {
|
|||||||
badge: data.badge || '/icon-192.png',
|
badge: data.badge || '/icon-192.png',
|
||||||
tag: data.tag || 'vibetorrent-notification',
|
tag: data.tag || 'vibetorrent-notification',
|
||||||
requireInteraction: false,
|
requireInteraction: false,
|
||||||
|
// iOS-specific: vibrate pattern (if supported)
|
||||||
|
vibrate: [200, 100, 200],
|
||||||
|
// Add data for notification click handling
|
||||||
|
data: {
|
||||||
|
url: data.url || '/',
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[Service Worker] Showing notification:', title, options);
|
console.log('[Service Worker] Showing notification:', title, options);
|
||||||
|
|||||||
Reference in New Issue
Block a user