Email notifications: settings UI + mail adapter #4

Open
hudson wants to merge 27 commits from email-notifications into dev
Showing only changes of commit d6f0e47edf - Show all commits

View file

@ -1,4 +1,4 @@
import {hash, maybe, spec, throttle} from "@welshman/lib"
import {call, hash, maybe, spec, throttle} from "@welshman/lib"
import type {Filter} from "@welshman/util"
import {Address, DELETE, MESSAGE, makeEvent, makeHttpAuth, makeHttpAuthHeader} from "@welshman/util"
import {merged} from "@welshman/store"
@ -75,50 +75,70 @@ const syncRelaySubscriptionsForEmail = (
export class MailNotifications {
_controller = maybe<AbortController>()
// In-flight register promise, so overlapping calls (settings page submit plus
// Mail.sync) coalesce into a single PUT instead of racing two subscriptions.
_register = maybe<Promise<void>>()
async register(email: string, frequency: string) {
const $user = User.require(app.get())
const $signer = $user.signer
if (this._register) return this._register
if (!$signer) {
emailNotificationState.set({error: "No signer available. Check your browser extension."})
throw new Error("No signer available")
}
this._register = call(async () => {
try {
const $user = User.require(app.get())
const $signer = $user.signer
if (!EMAIL_NOTIFICATION_SERVER) {
emailNotificationState.set({error: "Email notification server is not configured."})
throw new Error("Email notification server not configured")
}
if (!$signer) {
emailNotificationState.set({
error: "No signer available. Check your browser extension.",
})
throw new Error("No signer available")
}
let authEvent
try {
authEvent = await $signer.sign(await makeHttpAuth(EMAIL_NOTIFICATION_SERVER, "PUT"))
} catch (e) {
emailNotificationState.set({error: "Failed to create authentication. Check your signer."})
throw new Error("Failed to sign NIP-98 auth", {cause: e})
}
if (!EMAIL_NOTIFICATION_SERVER) {
emailNotificationState.set({error: "Email notification server is not configured."})
throw new Error("Email notification server not configured")
}
const authHeader = makeHttpAuthHeader(authEvent)
let authEvent
try {
authEvent = await $signer.sign(await makeHttpAuth(EMAIL_NOTIFICATION_SERVER, "PUT"))
} catch (e) {
emailNotificationState.set({
error: "Failed to create authentication. Check your signer.",
})
throw new Error("Failed to sign NIP-98 auth", {cause: e})
}
let res
try {
res = await fetch(buildUrl(EMAIL_NOTIFICATION_SERVER, "subscription", "email"), {
method: "PUT",
body: JSON.stringify({email, frequency, pubkey: $user.pubkey}),
headers: {Authorization: authHeader, "Content-Type": "application/json"},
})
} catch (e) {
emailNotificationState.set({error: `Could not reach email server. Check your connection.`})
throw new Error("Network error", {cause: e})
}
const authHeader = makeHttpAuthHeader(authEvent)
if (!res.ok) {
const text = await res.text().catch(() => "unknown error")
emailNotificationState.set({error: `Registration failed: ${text}`})
throw new Error(`Registration failed: ${text}`)
}
let res
try {
res = await fetch(buildUrl(EMAIL_NOTIFICATION_SERVER, "subscription", "email"), {
method: "PUT",
body: JSON.stringify({email, frequency, pubkey: $user.pubkey}),
headers: {Authorization: authHeader, "Content-Type": "application/json"},
})
} catch (e) {
emailNotificationState.set({
error: `Could not reach email server. Check your connection.`,
})
throw new Error("Network error", {cause: e})
}
const {key, callback} = await res.json()
emailNotificationState.set({key, callback, email, frequency, error: undefined})
if (!res.ok) {
const text = await res.text().catch(() => "unknown error")
emailNotificationState.set({error: `Registration failed: ${text}`})
throw new Error(`Registration failed: ${text}`)
}
const {key, callback} = await res.json()
emailNotificationState.set({key, callback, email, frequency, error: undefined})
} finally {
this._register = undefined
}
})
return this._register
}
async unregister() {