learning_ai_common_plat/packages/push/src/providers/expo.ts

65 lines
1.8 KiB
TypeScript

/**
* 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<PushResult> {
const results = await this.sendBatch([notification]);
return results[0]!;
}
async sendBatch(notifications: PushNotification[]): Promise<PushResult[]> {
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',
}));
}
}
}