fix(front): try to correct crash on ios
Some checks failed
Deploy to Development / build-and-deploy (push) Has been cancelled
Deploy to Production / build-and-deploy (push) Successful in 1m39s

This commit is contained in:
EvanChal
2026-01-26 23:03:49 +01:00
parent 08810440e0
commit 658b7a9dda
10 changed files with 149 additions and 48 deletions

View File

@@ -200,11 +200,7 @@ async function submitTicket() {
console.log(`DEBUG - FormData entry: ${key} = ${value}`) console.log(`DEBUG - FormData entry: ${key} = ${value}`)
} }
await axios.post('/api/tickets/', formData, { await axios.post('/api/tickets/', formData)
headers: {
'Content-Type': 'multipart/form-data',
}
})
toast.success('Ticket envoyé avec succès !') toast.success('Ticket envoyé avec succès !')
closeModal() closeModal()

View File

@@ -442,10 +442,20 @@ onMounted(async () => {
// Demander la permission pour les notifications push // Demander la permission pour les notifications push
// Sur iOS, attendre un peu pour s'assurer que la PWA est bien détectée // Sur iOS, attendre un peu pour s'assurer que la PWA est bien détectée
if (typeof notificationService.isIOS === 'function' && notificationService.isIOS()) { if (typeof notificationService.isIOS === 'function' && notificationService.isIOS()) {
// Attendre que la page soit complètement chargée console.log('🍎 iOS détecté, vérification de la PWA...')
// Attendre que la page soit complètement chargée et que la PWA soit détectée
setTimeout(async () => { setTimeout(async () => {
const isInstalled = notificationService.isPWAInstalled()
console.log('🍎 PWA installée:', isInstalled)
if (isInstalled) {
console.log('🍎 Demande de permission pour les notifications...')
await notificationService.requestNotificationPermission() await notificationService.requestNotificationPermission()
}, 1000) } else {
console.warn('🍎 PWA non installée - Les notifications ne fonctionneront pas sur iOS')
console.warn('🍎 Instructions: Ajouter l\'app à l\'écran d\'accueil, puis l\'ouvrir depuis l\'écran d\'accueil')
}
}, 2000) // Attendre 2 secondes pour iOS
} else { } else {
await notificationService.requestNotificationPermission() await notificationService.requestNotificationPermission()
} }

View File

@@ -130,56 +130,102 @@ class NotificationService {
// Gestion des notifications push PWA // Gestion des notifications push PWA
async requestNotificationPermission() { async requestNotificationPermission() {
console.log('🔔 requestNotificationPermission appelée')
console.log('🔔 isIOS:', this.isIOS())
console.log('🔔 isPWAInstalled:', this.isPWAInstalled())
console.log('🔔 Notification API disponible:', 'Notification' in window)
console.log('🔔 Permission actuelle:', Notification.permission)
if (!this.isNotificationSupported()) { if (!this.isNotificationSupported()) {
console.log('Notifications not supported on this platform') console.warn('⚠️ Notifications not supported on this platform')
if (this.isIOS()) {
if (!this.isPWAInstalled()) {
console.warn('⚠️ iOS: PWA must be installed (added to home screen)')
}
const iosVersion = navigator.userAgent.match(/OS (\d+)_(\d+)/)
if (iosVersion) {
const major = parseInt(iosVersion[1], 10)
const minor = parseInt(iosVersion[2], 10)
if (major < 16 || (major === 16 && minor < 4)) {
console.warn(`⚠️ iOS: Version ${major}.${minor} - Push notifications require iOS 16.4+`)
}
}
}
return false return false
} }
if (Notification.permission === 'granted') { if (Notification.permission === 'granted') {
console.log('Notification permission already granted') console.log('Notification permission already granted')
return true return true
} }
if (Notification.permission === 'denied') { if (Notification.permission === 'denied') {
console.warn('Notification permission denied by user') console.warn('Notification permission denied by user')
return false return false
} }
// Sur iOS, s'assurer que la PWA est installée avant de demander // Sur iOS, s'assurer que la PWA est installée avant de demander
if (this.isIOS() && !this.isPWAInstalled()) { if (this.isIOS() && !this.isPWAInstalled()) {
console.warn('iOS: Cannot request notification permission - PWA must be installed first') console.warn('⚠️ iOS: Cannot request notification permission - PWA must be installed first')
console.warn('⚠️ Instructions: Add the app to home screen, then open it from home screen')
return false return false
} }
try { try {
console.log('🔔 Demande de permission...')
const permission = await Notification.requestPermission() const permission = await Notification.requestPermission()
const granted = permission === 'granted' const granted = permission === 'granted'
console.log('🔔 Résultat de la demande:', permission)
if (granted) { if (granted) {
console.log('Notification permission granted') console.log('Notification permission granted')
// Sur iOS, vérifier que le service worker est prêt
if (this.isIOS() && 'serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.ready
console.log('✅ Service worker ready on iOS')
// Tester une notification pour vérifier que ça fonctionne
await registration.showNotification('Test LeDiscord', {
body: 'Les notifications sont activées !',
icon: '/icon-192x192.png',
badge: '/icon-96x96.png',
tag: 'test-notification'
})
console.log('✅ Test notification sent successfully')
} catch (error) {
console.error('❌ Error testing notification on iOS:', error)
}
}
} else { } else {
console.warn('Notification permission denied:', permission) console.warn('Notification permission denied:', permission)
} }
return granted return granted
} catch (error) { } catch (error) {
console.error('Error requesting notification permission:', error) console.error('Error requesting notification permission:', error)
return false return false
} }
} }
async showPushNotification(title, options = {}) { async showPushNotification(title, options = {}) {
console.log('🔔 showPushNotification appelée:', { title, options })
if (!this.isNotificationSupported()) { if (!this.isNotificationSupported()) {
console.warn('Cannot show notification - not supported on this platform') console.warn('⚠️ Cannot show notification - not supported on this platform')
return null return null
} }
const hasPermission = await this.requestNotificationPermission() const hasPermission = await this.requestNotificationPermission()
if (!hasPermission) { if (!hasPermission) {
console.warn('Cannot show notification - permission not granted') console.warn('⚠️ Cannot show notification - permission not granted')
return null return null
} }
console.log('✅ Permission granted, affichage de la notification...')
// Préparer les options de notification (iOS ne supporte pas vibrate) // Préparer les options de notification (iOS ne supporte pas vibrate)
const notificationOptions = { const notificationOptions = {
body: options.body || options.message || '', body: options.body || options.message || '',
@@ -203,13 +249,22 @@ class NotificationService {
// Cela permet aux notifications de fonctionner même quand l'app est fermée // Cela permet aux notifications de fonctionner même quand l'app est fermée
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
try { try {
console.log('🔔 Tentative d\'affichage via service worker...')
const registration = await navigator.serviceWorker.ready const registration = await navigator.serviceWorker.ready
console.log('🔔 Service worker ready, active:', !!registration.active)
if (!registration.active) { if (!registration.active) {
console.warn('Service worker not active, using fallback') console.warn('⚠️ Service worker not active, using fallback')
throw new Error('Service worker not active') throw new Error('Service worker not active')
} }
// Sur iOS, utiliser directement l'API du service worker
// (les messages peuvent ne pas fonctionner correctement)
if (this.isIOS()) {
console.log('🔔 iOS: Utilisation directe de showNotification')
await registration.showNotification(title, notificationOptions)
console.log('✅ Notification affichée via service worker (iOS)')
} else {
// Envoyer un message au service worker pour afficher la notification // Envoyer un message au service worker pour afficher la notification
registration.active.postMessage({ registration.active.postMessage({
type: 'SHOW_NOTIFICATION', type: 'SHOW_NOTIFICATION',
@@ -219,13 +274,21 @@ class NotificationService {
// Aussi utiliser l'API directe du service worker // Aussi utiliser l'API directe du service worker
await registration.showNotification(title, notificationOptions) await registration.showNotification(title, notificationOptions)
console.log('✅ Notification affichée via service worker')
}
console.log('Notification shown via service worker')
return null return null
} catch (error) { } catch (error) {
console.error('Error showing notification via service worker:', error) console.error('Error showing notification via service worker:', error)
console.error('❌ Error details:', {
message: error.message,
stack: error.stack,
name: error.name
})
// Continuer avec le fallback // Continuer avec le fallback
} }
} else {
console.warn('⚠️ Service worker not available')
} }
// Fallback: notification native du navigateur (seulement si le SW n'est pas disponible) // Fallback: notification native du navigateur (seulement si le SW n'est pas disponible)
@@ -270,6 +333,23 @@ class NotificationService {
}) })
} }
} }
// Fonction de test pour vérifier que les notifications fonctionnent
async testNotification() {
console.log('🧪 Test de notification...')
const result = await this.showPushNotification('Test LeDiscord', {
body: 'Si vous voyez cette notification, les notifications push fonctionnent !',
link: '/'
})
if (result) {
console.log('✅ Test réussi - notification affichée')
return true
} else {
console.warn('⚠️ Test échoué - notification non affichée')
return false
}
}
} }
export default new NotificationService() export default new NotificationService()

View File

@@ -112,11 +112,7 @@ export const useAuthStore = defineStore('auth', () => {
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('file', file)
const response = await axios.post('/api/users/me/avatar', formData, { const response = await axios.post('/api/users/me/avatar', formData)
headers: {
'Content-Type': 'multipart/form-data'
}
})
user.value = response.data user.value = response.data
toast.success('Avatar mis à jour') toast.success('Avatar mis à jour')

View File

@@ -68,6 +68,16 @@ instance.interceptors.request.use(
} }
} }
// Augmenter le timeout pour les requêtes POST/PUT avec FormData (uploads)
if ((config.method === 'POST' || config.method === 'PUT') && config.data instanceof FormData) {
config.timeout = 120000 // 2 minutes pour les uploads
// Ne pas définir Content-Type pour FormData - laisser le navigateur l'ajouter avec la boundary
// C'est crucial sur mobile où définir explicitement le Content-Type peut causer des erreurs
if (config.headers && config.headers['Content-Type'] === 'multipart/form-data') {
delete config.headers['Content-Type']
}
}
return config return config
}, },
error => { error => {
@@ -112,12 +122,24 @@ instance.interceptors.response.use(
} }
if (error.response?.status === 401) { if (error.response?.status === 401) {
// Ne pas déconnecter automatiquement sur les requêtes POST/PUT avec FormData
// car les erreurs peuvent être dues à des problèmes réseau temporaires sur mobile
const isFormDataUpload = error.config?.data instanceof FormData ||
(error.config?.method === 'POST' || error.config?.method === 'PUT')
// Ne pas rediriger si on est déjà sur une page d'auth // Ne pas rediriger si on est déjà sur une page d'auth
const currentRoute = router.currentRoute.value const currentRoute = router.currentRoute.value
if (!currentRoute.path.includes('/login') && !currentRoute.path.includes('/register')) { if (!currentRoute.path.includes('/login') && !currentRoute.path.includes('/register')) {
// Pour les uploads, ne déconnecter que si c'est vraiment une erreur d'authentification
// (pas juste une erreur réseau qui se manifeste comme 401)
if (!isFormDataUpload || error.response?.data?.detail?.includes('credentials') || error.response?.data?.detail?.includes('token')) {
localStorage.removeItem('token') localStorage.removeItem('token')
router.push('/login') router.push('/login')
toast.error('Session expirée, veuillez vous reconnecter') toast.error('Session expirée, veuillez vous reconnecter')
} else {
// Pour les uploads, juste afficher une erreur sans déconnecter
toast.error('Erreur lors de l\'upload. Veuillez réessayer.')
}
} }
} else if (error.response?.status === 403) { } else if (error.response?.status === 403) {
toast.error('Accès non autorisé') toast.error('Accès non autorisé')

View File

@@ -716,9 +716,7 @@ async function uploadMedia() {
formData.append('files', media.file) formData.append('files', media.file)
}) })
await axios.post(`/api/albums/${album.value.id}/media`, formData, { await axios.post(`/api/albums/${album.value.id}/media`, formData)
headers: { 'Content-Type': 'multipart/form-data' }
})
// Refresh album data // Refresh album data
await fetchAlbum() await fetchAlbum()

View File

@@ -654,7 +654,6 @@ async function createAlbum() {
}) })
await axios.post(`/api/albums/${album.id}/media`, formData, { await axios.post(`/api/albums/${album.id}/media`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => { onUploadProgress: (progressEvent) => {
// Update progress for this batch // Update progress for this batch
const batchProgress = (progressEvent.loaded / progressEvent.total) * 100 const batchProgress = (progressEvent.loaded / progressEvent.total) * 100

View File

@@ -465,9 +465,7 @@ async function handleImageChange(event) {
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('file', file)
const response = await axios.post('/api/posts/upload-image', formData, { const response = await axios.post('/api/posts/upload-image', formData)
headers: { 'Content-Type': 'multipart/form-data' }
})
newPost.value.image_url = response.data.image_url newPost.value.image_url = response.data.image_url
} catch (error) { } catch (error) {

View File

@@ -435,9 +435,7 @@ async function createVlog() {
formData.append('thumbnail', newVlog.value.thumbnailFile) formData.append('thumbnail', newVlog.value.thumbnailFile)
} }
const response = await axios.post('/api/vlogs/upload', formData, { const response = await axios.post('/api/vlogs/upload', formData)
headers: { 'Content-Type': 'multipart/form-data' }
})
vlogs.value.unshift(response.data) vlogs.value.unshift(response.data)
showCreateModal.value = false showCreateModal.value = false

View File

@@ -211,7 +211,11 @@ module.exports = defineConfig(({ command, mode }) => {
} }
}, },
{ {
urlPattern: /^https?:\/\/.*\/api\/.*/i, // Intercepter uniquement les requêtes GET pour l'API
urlPattern: ({ url, request }) => {
const urlString = url.href || url.toString()
return /^https?:\/\/.*\/api\/.*/i.test(urlString) && request.method === 'GET'
},
handler: 'NetworkFirst', handler: 'NetworkFirst',
options: { options: {
cacheName: 'api-cache', cacheName: 'api-cache',