/** * Expo push notification provider. * * Uses the Expo push notification service REST API. * No SDK dependency — just HTTP requests. */ import type { PushNotification, PushProvider, PushResult } from '../types.js'; const EXPO_PUSH_URL = 'https://exp.host/--/api/v2/push/send'; export class ExpoPushProvider implements PushProvider { isConfigured(): boolean { return true; // Expo push is open — no API key required for basic use } async send(notification: PushNotification): Promise { const results = await this.sendBatch([notification]); return results[0]!; } async sendBatch(notifications: PushNotification[]): Promise { const messages = notifications.map(n => ({ to: n.deviceToken, title: n.title, body: n.body, data: n.data, badge: n.badge, sound: n.sound ?? 'default', channelId: n.channelId, })); try { const response = await fetch(EXPO_PUSH_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(messages), }); if (!response.ok) { const text = await response.text(); return notifications.map(() => ({ success: false, error: `Expo push error ${response.status}: ${text}`, })); } const data = (await response.json()) as { data: Array<{ status: string; id?: string; message?: string }>; }; return data.data.map(ticket => ({ success: ticket.status === 'ok', messageId: ticket.id, error: ticket.status !== 'ok' ? ticket.message : undefined, })); } catch (err) { return notifications.map(() => ({ success: false, error: err instanceof Error ? err.message : 'Unknown error', })); } } }