Email notifications: settings UI + mail adapter #4
1 changed files with 71 additions and 6 deletions
|
|
@ -1,12 +1,76 @@
|
|||
import {hash, maybe, throttle} from "@welshman/lib"
|
||||
import {hash, maybe, spec, throttle} from "@welshman/lib"
|
||||
import type {Filter} from "@welshman/util"
|
||||
import {Address, DELETE, makeEvent, makeHttpAuth, makeHttpAuthHeader} from "@welshman/util"
|
||||
import {Address, DELETE, MESSAGE, makeEvent, makeHttpAuth, makeHttpAuthHeader} from "@welshman/util"
|
||||
import {merged} from "@welshman/store"
|
||||
import {Relays, User} from "@welshman/app"
|
||||
import {buildUrl} from "@lib/util"
|
||||
import {app, thunks} from "@app/core"
|
||||
import {app, messagingRelayLists, roomLists, thunks} from "@app/core"
|
||||
import {EMAIL_NOTIFICATION_SERVER, PUSH_BRIDGE} from "@app/env"
|
||||
import {ensureEmailSalt, userSettingsValues} from "@app/settings"
|
||||
import {emailNotificationState, syncRelaySubscriptions} from "@app/push/adapters/common"
|
||||
import {emailNotificationState} from "@app/push/adapters/common"
|
||||
import {DM_KINDS, CONTENT_KINDS, makeCommentFilter} from "@app/content"
|
||||
|
||||
// Email digests sync their own kind 30390 subscriptions. Unlike push, the email
|
||||
// subscription is driven by the email digest setting (SettingsValues.email) plus
|
||||
// per-space alert preferences, and must NOT be gated on the push notificationSettings
|
||||
// toggles - otherwise turning off push "new activity" would delete the 30390 and stop
|
||||
// email digests entirely.
|
||||
const syncRelaySubscriptionsForEmail = (
|
||||
signal: AbortSignal,
|
||||
sync: (url: string, key: string, filters: Filter[], ignore: Filter[]) => void,
|
||||
) => {
|
||||
const $pubkey = User.require(app.get()).pubkey
|
||||
|
||||
const unsubscribeSpaces = merged([roomLists.get().urls($pubkey).$, userSettingsValues]).subscribe(
|
||||
throttle(3000, ([$spaceUrls, {email, alerts}]) => {
|
||||
const baseFilters = [{kinds: [MESSAGE, ...CONTENT_KINDS]}, makeCommentFilter(CONTENT_KINDS)]
|
||||
|
||||
for (const url of $spaceUrls) {
|
||||
const filters: Filter[] = []
|
||||
const ignore: Filter[] = []
|
||||
|
||||
if (!email) {
|
||||
sync(url, "spaces", filters, [])
|
||||
continue
|
||||
}
|
||||
|
||||
const {notify = true, exceptions = []} = alerts.find(spec({url})) || {}
|
||||
|
||||
if (notify) {
|
||||
if (exceptions.length > 0) {
|
||||
ignore.push({"#h": exceptions})
|
||||
}
|
||||
filters.push(...baseFilters)
|
||||
} else if (exceptions.length > 0) {
|
||||
filters.push(...baseFilters.map(f => ({...f, "#h": exceptions})))
|
||||
}
|
||||
|
||||
// Mentions are part of the digest regardless of push settings, and
|
||||
// guarantee there is always at least one filter so the subscription
|
||||
// stays live while email digests are enabled.
|
||||
filters.push(...baseFilters.map(f => ({...f, "#p": [$pubkey]})))
|
||||
|
||||
sync(url, "spaces", filters, ignore)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
const unsubscribeMessages = merged([
|
||||
messagingRelayLists.get().urls($pubkey).$,
|
||||
userSettingsValues,
|
||||
]).subscribe(
|
||||
throttle(3000, ([$messagingUrls, {email}]) => {
|
||||
for (const url of $messagingUrls) {
|
||||
sync(url, "messages", email ? [{kinds: DM_KINDS, "#p": [$pubkey]}] : [], [])
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
signal.addEventListener("abort", () => {
|
||||
unsubscribeSpaces()
|
||||
unsubscribeMessages()
|
||||
})
|
||||
}
|
||||
|
||||
export class MailNotifications {
|
||||
_controller = maybe<AbortController>()
|
||||
|
|
@ -96,8 +160,9 @@ export class MailNotifications {
|
|||
await this.register(email, frequency)
|
||||
}
|
||||
|
||||
// Sync kind 30390 subscriptions using the same filters as push
|
||||
syncRelaySubscriptions(
|
||||
// Sync kind 30390 subscriptions for the email digest. Filters are derived
|
||||
// from the email setting + per-space alerts, independent of push toggles.
|
||||
syncRelaySubscriptionsForEmail(
|
||||
this._controller.signal,
|
||||
async (url: string, key: string, filters: Filter[], ignore: Filter[]) => {
|
||||
const {callback} = emailNotificationState.get()
|
||||
|
|
|
|||
Loading…
Reference in a new issue