/** * usePasswordlessAuth — hanterar push-notifieringar och deep links * för passwordless cross-device authentication */ import { useState, useEffect, useCallback, useRef } from 'react' import { Platform } from 'react-native' import * as Notifications from 'expo-notifications' import * as Linking from 'expo-linking' import type { Subscription } from 'expo-notifications' import type { EventType } from 'expo-linking' import { passwordless } from '../../../lib/api' export interface AuthRequest { requestToken: string deviceInfo?: { name?: string browser?: string type?: string } } export function usePasswordlessAuth( onRequest: (request: AuthRequest) => void, onApproved: () => void, onDenied: () => void, ) { const [pushToken, setPushToken] = useState(null) const notificationListener = useRef(null) const responseListener = useRef(null) const urlListener = useRef<{ remove: () => void } | null>(null) // Register for push notifications useEffect(() => { registerForPushNotifications() return () => { if (notificationListener.current) { Notifications.removeNotificationSubscription(notificationListener.current) } if (responseListener.current) { Notifications.removeNotificationSubscription(responseListener.current) } if (urlListener.current) { urlListener.current.remove() } } }, []) // Listen for incoming notifications useEffect(() => { // Foreground notification received notificationListener.current = Notifications.addNotificationReceivedListener( (notification: Notifications.Notification) => { handleNotification(notification.request.content.data as Record) }, ) // Notification tapped responseListener.current = Notifications.addNotificationResponseReceivedListener( (response: Notifications.NotificationResponse) => { handleNotification(response.notification.request.content.data as Record) }, ) // Deep links urlListener.current = Linking.addEventListener('url', (event: EventType) => { handleDeepLink(event.url) }) // Check initial URL (app opened via deep link) Linking.getInitialURL().then((url: string | null) => { if (url) handleDeepLink(url) }) return () => { if (notificationListener.current) { Notifications.removeNotificationSubscription(notificationListener.current) } if (responseListener.current) { Notifications.removeNotificationSubscription(responseListener.current) } if (urlListener.current) { urlListener.current.remove() } } }, [onRequest, onApproved, onDenied]) const handleNotification = useCallback( (data: Record | undefined) => { if (!data) return if (data.type === 'passwordless_auth') { const requestToken = data.request_token as string const deviceInfo = data.device_info as AuthRequest['deviceInfo'] | undefined if (requestToken) { onRequest({ requestToken, deviceInfo }) } } if (data.type === 'passwordless_approved') { onApproved() } if (data.type === 'passwordless_denied') { onDenied() } }, [onRequest, onApproved, onDenied], ) const handleDeepLink = useCallback( (url: string) => { const parsed = Linking.parse(url) if (parsed.scheme === 'quixzoom' && parsed.path === 'auth/approve') { const requestToken = parsed.queryParams?.token as string | undefined const deviceName = parsed.queryParams?.device as string | undefined if (requestToken) { onRequest({ requestToken, deviceInfo: { name: deviceName || 'quixzoom.se', type: 'web', }, }) } } }, [onRequest], ) const registerForPushNotifications = async () => { try { const { status: existingStatus } = await Notifications.getPermissionsAsync() let finalStatus = existingStatus if (existingStatus !== 'granted') { const { status } = await Notifications.requestPermissionsAsync() finalStatus = status } if (finalStatus !== 'granted') { console.log('[usePasswordlessAuth] Push permission not granted') return } const token = await Notifications.getExpoPushTokenAsync({ projectId: 'quixzoom', }) setPushToken(token.data) // Register push token with backend await registerPushToken(token.data) } catch (error) { console.error('[usePasswordlessAuth] Failed to register push:', error) } } const registerPushToken = async (token: string) => { try { await fetch('https://api.quixzoom.com/auth/push-token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token, platform: Platform.OS, }), }) } catch (e) { console.error('[usePasswordlessAuth] Failed to register push token:', e) } } return { pushToken } }