Fix bug where email 30390s would be deleted if push notifications were toggled off.
Some checks failed
CI / lint-check-build (pull_request) Has been cancelled

This commit is contained in:
mplorentz 2026-09-03 13:04:29 -04:00
parent 42561c8c02
commit cbba1035e7

View file

@ -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 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 {Relays, User} from "@welshman/app"
import {buildUrl} from "@lib/util" 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 {EMAIL_NOTIFICATION_SERVER, PUSH_BRIDGE} from "@app/env"
import {ensureEmailSalt, userSettingsValues} from "@app/settings" 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 { export class MailNotifications {
_controller = maybe<AbortController>() _controller = maybe<AbortController>()
@ -96,8 +160,9 @@ export class MailNotifications {
await this.register(email, frequency) await this.register(email, frequency)
} }
// Sync kind 30390 subscriptions using the same filters as push // Sync kind 30390 subscriptions for the email digest. Filters are derived
syncRelaySubscriptions( // from the email setting + per-space alerts, independent of push toggles.
syncRelaySubscriptionsForEmail(
this._controller.signal, this._controller.signal,
async (url: string, key: string, filters: Filter[], ignore: Filter[]) => { async (url: string, key: string, filters: Filter[], ignore: Filter[]) => {
const {callback} = emailNotificationState.get() const {callback} = emailNotificationState.get()