import type { PrismaClient } from "./client.js"; export type PushSubscriptionKind = "webpush" | "expo"; /** Devices one user keeps registered; the least recently registered ones are dropped first. */ export const PUSH_SUBSCRIPTION_LIMIT = 20; export interface StoredPushSubscription { id: string; kind: PushSubscriptionKind; endpoint: string; p256dh: string | null; auth: string | null; } export interface NewPushSubscription { kind: PushSubscriptionKind; /** Web push endpoint URL or Expo push token. */ endpoint: string; p256dh?: string | null; auth?: string | null; userAgent?: string; } /** * Registers a device for the user. An endpoint registered by another account moves to this one: * a device delivers to whoever signed in on it last. */ export async function savePushSubscription( prisma: PrismaClient, userId: string, input: NewPushSubscription, ): Promise { const data = { userId, kind: input.kind, p256dh: input.p256dh ?? null, auth: input.auth ?? null, userAgent: (input.userAgent ?? "").slice(0, 256), }; await prisma.pushSubscription.upsert({ where: { endpoint: input.endpoint }, create: { endpoint: input.endpoint, ...data }, // A renewed registration counts as the newest device for the per-user limit. update: { ...data, createdAt: new Date() }, }); const stale = await prisma.pushSubscription.findMany({ where: { userId }, orderBy: [{ createdAt: "desc" }, { id: "desc" }], skip: PUSH_SUBSCRIPTION_LIMIT, select: { id: true }, }); if (stale.length > 0) { await prisma.pushSubscription.deleteMany({ where: { id: { in: stale.map((row) => row.id) } }, }); } } /** Removes one of the user's devices; another user's endpoint is left alone. */ export async function removePushSubscription( prisma: PrismaClient, userId: string, endpoint: string, ): Promise { await prisma.pushSubscription.deleteMany({ where: { userId, endpoint } }); } /** Removes every device of one kind, e.g. all mobile app tokens on sign-out. */ export async function removePushSubscriptionsOfKind( prisma: PrismaClient, userId: string, kind: PushSubscriptionKind, ): Promise { await prisma.pushSubscription.deleteMany({ where: { userId, kind } }); } export async function listPushSubscriptions( prisma: PrismaClient, userId: string, ): Promise { const rows = await prisma.pushSubscription.findMany({ where: { userId }, orderBy: { createdAt: "asc" }, select: { id: true, kind: true, endpoint: true, p256dh: true, auth: true }, }); return rows.map((row) => ({ ...row, kind: row.kind as PushSubscriptionKind })); }