/** * Native FCM push for the bundled Capacitor app (Phase B) — rings the device on a new chat DM when * the app is backgrounded/killed (the live WebSocket only covers an OPEN app; web-push doesn't reach * a native app). * * App-only + staff-only + fail-soft: * - early-returns on the web build (__CAP_BUILD__ undefined) and the plugin is also dropped from the * web bundle by the nuxt.config app:resolve hook; * - @capacitor/push-notifications is DYNAMICALLY imported so the web build never loads it; * - registers only once the user is eligible staff (useChatEligible — same gate as the chat socket); * - any failure (no google-services.json, perm denied, …) is swallowed → native push just stays off. * * NOTE: the filename intentionally avoids the substring 'push-notifications' — the app:resolve drop * list strips that (it targets the WEB VAPID plugin 02.push-notifications.client.ts). */ // Injected by Vite only in the app build (see nuxt.config.ts). declare const __CAP_BUILD__: boolean | undefined export default defineNuxtPlugin(() => { if (typeof __CAP_BUILD__ === 'undefined') return // web build → no-op if (!process.client) return if (useRuntimeConfig().public.chatEnabled !== true) return const eligible = useChatEligible() let registered = false const setup = async () => { if (registered || !eligible.value) return registered = true try { const { PushNotifications } = await import('@capacitor/push-notifications') // Token issued → persist it for this staff user (server resolves the user from the bearer). PushNotifications.addListener('registration', (token: any) => { $fetch('/api/push/register-native', { method: 'POST', body: { token: token?.value, platform: 'android' }, }).catch((e: any) => console.warn('[native-push] token register failed:', e?.message)) }) PushNotifications.addListener('registrationError', (err: any) => console.warn('[native-push] registration error:', err?.error)) // Tap on a notification → open that conversation. PushNotifications.addListener('pushNotificationActionPerformed', (action: any) => { const sender = action?.notification?.data?.senderUserId navigateTo(sender ? `/chat?chat=${sender}` : '/chat') }) // Foreground receipt is intentionally a no-op — the live WS already renders the message in-app. // High-importance channel so chat pushes can heads-up on Android 8+ (FCM targets channelId 'chat'). try { await PushNotifications.createChannel({ id: 'chat', name: 'Chat', importance: 5, visibility: 1 }) } catch { /* iOS / unsupported */ } let perm = await PushNotifications.checkPermissions() if (perm.receive === 'prompt' || perm.receive === 'prompt-with-rationale') { perm = await PushNotifications.requestPermissions() } if (perm.receive !== 'granted') { registered = false; return } // allow a later retry await PushNotifications.register() } catch (e: any) { registered = false console.warn('[native-push] setup failed:', e?.message) } } // Register as soon as the user is eligible (immediately if already logged in, else after login). watch(eligible, (v) => { if (v) setup() }, { immediate: true }) })