import {MINUTE, first, formatTimestampRelative, int, ms, now, uniq} from "@welshman/lib" import {EVENT_RSVP, getIdFilters, isSignedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util" import {CalendarRsvp} from "@welshman/domain" import {getEventStart, getEventTitle, isAttending} from "@app/calendar" import {app, network, reader, roomLists} from "@app/core" import {notificationSettings, pushState} from "@app/push/adapters/common" import {deleteReminderSchedule, putReminderSchedule} from "@app/push/server" import type {Reminder} from "@app/push/server" const getLeadTimes = () => { const {events, reminder_minutes} = notificationSettings.get() return events ? uniq(reminder_minutes) : [] } const makeReminders = (event: TrustedEvent, start: number): Reminder[] => getLeadTimes() .map(minutes => ({ at: start - int(minutes, MINUTE), title: getEventTitle(event) || "Upcoming event", body: `Starts ${formatTimestampRelative(now() + int(minutes, MINUTE))}`, })) .filter(({at}) => at > now()) const putSchedule = async (relay: string, event: TrustedEvent, reminders: Reminder[]) => { const {subscription} = pushState.get() if (subscription) { const res = await putReminderSchedule(subscription.key, { id: event.id, relay, event: isSignedEvent(event) ? event : undefined, notifications: reminders, }) if (!res.ok) { console.warn(`Failed to schedule event reminders (status ${res.status})`) } } } export const setEventReminders = (relay: string, event: TrustedEvent, attending: boolean) => { const start = getEventStart(event) return putSchedule(relay, event, attending && start ? makeReminders(event, start) : []) } const loadAttendedEvents = async (pubkey: string) => { const $network = network.get() const signal = AbortSignal.timeout(ms(10)) const filters = [{kinds: [EVENT_RSVP], authors: [pubkey]}] const relays = roomLists.get().urls(pubkey).get() await $network.request({signal, filters, relays, autoClose: true}) const {repository, tracker} = app.get() // An RSVP only goes to the space holding its event, so where it was seen is where the event is. const attended = repository.query(filters).flatMap(rsvp => { const rsvpReader = reader(CalendarRsvp)(rsvp) const address = rsvpReader.calendarEvent() const relay = first(Array.from(tracker.getRelays(rsvp.id))) return address && relay && isAttending(rsvpReader.status()) ? [{address, relay}] : [] }) await $network.request({ signal, autoClose: true, relays: uniq(attended.map(({relay}) => relay)), filters: getIdFilters(attended.map(({address}) => address)), }) return attended.flatMap(({address, relay}) => { const event = app.get().repository.getEvent(address) return event ? [{event, relay}] : [] }) } // Every schedule the push server holds was built from these settings, so redo the lot. export const syncEventReminders = async () => { const {subscription} = pushState.get() const pubkey = app.get().user?.pubkey if (subscription && pubkey) { const res = await deleteReminderSchedule(subscription.key) if (!res.ok) { console.warn(`Failed to clear event reminders (status ${res.status})`) } if (getLeadTimes().length > 0) { for (const {event, relay} of await loadAttendedEvents(pubkey)) { await setEventReminders(relay, event, true) } } } }