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:
spinline
2026-02-05 23:57:08 +03:00
parent 373da566be
commit bffc72391a
7 changed files with 217 additions and 0 deletions

View File

@@ -11,9 +11,13 @@
<!-- PWA & Mobile Capable -->
<meta name="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" />
<link rel="manifest" href="manifest.json" />
<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 -->
<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="0" />

View File

@@ -3,6 +3,7 @@
"short_name": "VibeTorrent",
"description": "Modern web-based torrent client with real-time updates",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#1d232a",
"theme_color": "#1d232a",

View File

@@ -16,6 +16,27 @@ pub fn App() -> impl IntoView {
// Wait a bit for service worker to be ready
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
let window = web_sys::window().expect("window should exist");
if let Ok(notification_class) = js_sys::Reflect::get(&window, &"Notification".into()) {

View File

@@ -1,6 +1,7 @@
use tailwind_fuse::merge::tw_merge;
pub mod notification;
pub mod platform;
pub fn cn(classes: impl AsRef<str>) -> String {
tw_merge(classes.as_ref())

View 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
}
}

View File

@@ -110,6 +110,13 @@ self.addEventListener('push', (event) => {
badge: data.badge || '/icon-192.png',
tag: data.tag || 'vibetorrent-notification',
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);