153 lines
3.9 KiB
TypeScript
153 lines
3.9 KiB
TypeScript
import fetch from 'node-fetch';
|
|
|
|
const EXPO_PUSH_URL = 'https://exp.host/--/api/v2/push/send';
|
|
|
|
export interface PushNotificationPayload {
|
|
to: string;
|
|
title: string;
|
|
body: string;
|
|
data?: Record<string, any>;
|
|
sound?: string;
|
|
badge?: number;
|
|
priority?: 'default' | 'normal' | 'high';
|
|
channelId?: string;
|
|
}
|
|
|
|
export interface PushTicket {
|
|
id: string;
|
|
status: 'ok' | 'error';
|
|
message?: string;
|
|
details?: {
|
|
error?: string;
|
|
};
|
|
}
|
|
|
|
export interface PushReceipt {
|
|
status: 'ok' | 'error';
|
|
message?: string;
|
|
details?: {
|
|
error?: string;
|
|
};
|
|
}
|
|
|
|
interface ExpoPushResponse {
|
|
data?: PushTicket[] | Record<string, PushReceipt>;
|
|
}
|
|
|
|
export async function sendPushNotification(payload: PushNotificationPayload): Promise<PushTicket> {
|
|
const response = await fetch(EXPO_PUSH_URL, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json',
|
|
},
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
const result = (await response.json()) as ExpoPushResponse;
|
|
if (result.data && Array.isArray(result.data)) {
|
|
return result.data[0] ?? { id: '', status: 'error', message: 'No ticket returned' };
|
|
}
|
|
return { id: '', status: 'error', message: 'No ticket returned' };
|
|
}
|
|
|
|
export async function sendPushNotifications(payloads: PushNotificationPayload[]): Promise<PushTicket[]> {
|
|
const chunks: PushNotificationPayload[][] = [];
|
|
for (let i = 0; i < payloads.length; i += 100) {
|
|
chunks.push(payloads.slice(i, i + 100));
|
|
}
|
|
|
|
const tickets: PushTicket[] = [];
|
|
for (const chunk of chunks) {
|
|
const response = await fetch(EXPO_PUSH_URL, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json',
|
|
},
|
|
body: JSON.stringify(chunk),
|
|
});
|
|
const result = (await response.json()) as ExpoPushResponse;
|
|
if (result.data && Array.isArray(result.data)) {
|
|
tickets.push(...result.data);
|
|
}
|
|
}
|
|
return tickets;
|
|
}
|
|
|
|
export async function getPushReceipts(ticketIds: string[]): Promise<PushReceipt[]> {
|
|
if (ticketIds.length === 0) return [];
|
|
|
|
const response = await fetch(`${EXPO_PUSH_URL}/receipts`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json',
|
|
},
|
|
body: JSON.stringify({ ids: ticketIds }),
|
|
});
|
|
|
|
const result = (await response.json()) as ExpoPushResponse;
|
|
const receipts: PushReceipt[] = [];
|
|
if (result.data && !Array.isArray(result.data)) {
|
|
for (const id of ticketIds) {
|
|
receipts.push(result.data[id] ?? { status: 'error', message: 'No receipt found' });
|
|
}
|
|
}
|
|
return receipts;
|
|
}
|
|
|
|
export function isExpoPushToken(token: string): boolean {
|
|
return token.startsWith('ExponentPushToken[') || token.startsWith('ExpoPushToken[');
|
|
}
|
|
|
|
export function createTaskReminderPayload(
|
|
pushToken: string,
|
|
taskTitle: string,
|
|
dueTime: string,
|
|
taskId: string,
|
|
repeat?: string
|
|
): PushNotificationPayload {
|
|
return {
|
|
to: pushToken,
|
|
title: taskTitle,
|
|
body: `Due ${dueTime}${repeat && repeat !== 'none' ? ' · repeating' : ''}`,
|
|
data: { type: 'task_reminder', taskId },
|
|
sound: 'default',
|
|
priority: 'high',
|
|
channelId: 'task-reminders',
|
|
};
|
|
}
|
|
|
|
export function createFriendRequestPayload(
|
|
pushToken: string,
|
|
fromUsername: string,
|
|
friendshipId: string
|
|
): PushNotificationPayload {
|
|
return {
|
|
to: pushToken,
|
|
title: 'New Friend Request',
|
|
body: `${fromUsername} wants to be your friend`,
|
|
data: { type: 'friend_request', friendshipId },
|
|
sound: 'default',
|
|
priority: 'high',
|
|
channelId: 'friend-requests',
|
|
};
|
|
}
|
|
|
|
export function createTaskAssignmentPayload(
|
|
pushToken: string,
|
|
taskTitle: string,
|
|
fromUsername: string,
|
|
taskId: string
|
|
): PushNotificationPayload {
|
|
return {
|
|
to: pushToken,
|
|
title: 'Task Assigned',
|
|
body: `${fromUsername} assigned you "${taskTitle}"`,
|
|
data: { type: 'task_assignment', taskId },
|
|
sound: 'default',
|
|
priority: 'high',
|
|
channelId: 'task-assignments',
|
|
};
|
|
} |