Remove old alerts

This commit is contained in:
Jon Staab
2026-01-19 16:33:16 -08:00
parent 9f34b33b7e
commit f85748fef9
17 changed files with 126 additions and 1080 deletions
+2 -180
View File
@@ -1,33 +1,24 @@
import {nwc} from "@getalby/sdk"
import * as nip19 from "nostr-tools/nip19"
import {get, derived} from "svelte/store"
import type {Override, MakeOptional} from "@welshman/lib"
import {
first,
sha256,
randomId,
append,
remove,
flatten,
poll,
uniq,
equals,
TIMEZONE,
LOCALE,
parseJson,
fromPairs,
last,
simpleCache,
normalizeUrl,
nthNe,
} from "@welshman/lib"
import {decrypt, Nip01Signer} from "@welshman/signer"
import {Nip01Signer} from "@welshman/signer"
import type {UploadTask} from "@welshman/editor"
import type {Feed} from "@welshman/feeds"
import {makeIntersectionFeed, feedFromFilters, makeRelayFeed} from "@welshman/feeds"
import type {TrustedEvent, EventContent, Profile} from "@welshman/util"
import {
WRAP,
DELETE,
REPORT,
PROFILE,
@@ -39,10 +30,6 @@ import {
RELAY_LEAVE,
ROOMS,
COMMENT,
ALERT_EMAIL,
ALERT_WEB,
ALERT_IOS,
ALERT_ANDROID,
APP_DATA,
isSignedEvent,
makeEvent,
@@ -55,8 +42,6 @@ import {
getRelayTagValues,
toNostrURI,
RelayMode,
getAddress,
getTagValue,
getTagValues,
uploadBlob,
canUploadBlob,
@@ -85,18 +70,15 @@ import {
waitForThunkError,
getPubkeyRelays,
userBlossomServerList,
shouldUnwrap,
getThunkError,
} from "@welshman/app"
import {compressFile} from "@lib/html"
import {kv, db} from "@app/core/storage"
import type {SettingsValues, Alert} from "@app/core/state"
import type {SettingsValues} from "@app/core/state"
import {
SETTINGS,
PROTECTED,
INDEXER_RELAYS,
NOTIFIER_PUBKEY,
NOTIFIER_RELAY,
DEFAULT_BLOSSOM_SERVERS,
userSpaceUrls,
userSettingsValues,
@@ -107,8 +89,6 @@ import {
relaysMostlyRestricted,
deriveSocket,
} from "@app/core/state"
import {loadAlertStatuses} from "@app/core/requests"
import {platform, platformName, getPushInfo} from "@app/util/push"
// Utils
@@ -373,164 +353,6 @@ export const makeComment = ({event, content, tags = []}: CommentParams) =>
export const publishComment = ({relays, ...params}: CommentParams & {relays: string[]}) =>
publishThunk({event: makeComment(params), relays})
// Alerts
export type AlertParamsEmail = {
cron: string
email: string
handler: string[]
}
export type AlertParamsWeb = {
endpoint: string
p256dh: string
auth: string
}
export type AlertParamsIos = {
device_token: string
bundle_identifier: string
}
export type AlertParamsAndroid = {
device_token: string
}
export type AlertParams = {
feed: Feed
description: string
claims?: Record<string, string>
email?: AlertParamsEmail
web?: AlertParamsWeb
ios?: AlertParamsIos
android?: AlertParamsAndroid
}
export const makeAlert = async (params: AlertParams) => {
const tags = [
["feed", JSON.stringify(params.feed)],
["locale", LOCALE],
["timezone", TIMEZONE],
["description", params.description],
]
for (const [relay, claim] of Object.entries(params.claims || [])) {
tags.push(["claim", relay, claim])
}
let kind: number
if (params.email) {
kind = ALERT_EMAIL
tags.push(...Object.entries(params.email).map(flatten))
} else if (params.web) {
kind = ALERT_WEB
tags.push(...Object.entries(params.web).map(flatten))
} else if (params.ios) {
kind = ALERT_IOS
tags.push(...Object.entries(params.ios).map(flatten))
} else if (params.android) {
kind = ALERT_ANDROID
tags.push(...Object.entries(params.android).map(flatten))
} else {
throw new Error("Alert has invalid params")
}
return makeEvent(kind, {
content: await signer.get().nip44.encrypt(NOTIFIER_PUBKEY, JSON.stringify(tags)),
tags: [
["d", randomId()],
["p", NOTIFIER_PUBKEY],
],
})
}
export const publishAlert = async (params: AlertParams) =>
publishThunk({event: await makeAlert(params), relays: [NOTIFIER_RELAY]})
export const deleteAlert = (alert: Alert) => {
const relays = [NOTIFIER_RELAY]
const tags = [["p", NOTIFIER_PUBKEY]]
return publishDelete({event: alert.event, relays, tags, protect: false})
}
export type CreateAlertParams = Override<
AlertParams,
{
email?: MakeOptional<AlertParamsEmail, "handler">
}
>
export type CreateAlertResult = {
ok?: true
error?: string
}
export const createAlert = async (params: CreateAlertParams): Promise<CreateAlertResult> => {
if (params.email) {
const cadence = params.email.cron.endsWith("1") ? "Weekly" : "Daily"
const handler = [
"31990:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:1737058597050",
"wss://relay.nostr.band/",
"web",
]
params.email = {handler, ...params.email}
params.description = `${cadence} alert ${params.description}, sent via email.`
} else {
try {
// @ts-ignore
params[platform] = await getPushInfo()
params.description = `${platformName} push notification ${params.description}.`
} catch (e: any) {
return {error: String(e)}
}
}
// If we don't do this we'll get an event rejection
await Pool.get().get(NOTIFIER_RELAY).auth.attemptAuth(sign)
const thunk = await publishAlert(params as AlertParams)
const error = await waitForThunkError(thunk)
if (error) {
return {error}
}
// Fetch our new status to make sure it's active
const $pubkey = pubkey.get()!
const address = getAddress(thunk.event)
const statusEvents = await loadAlertStatuses($pubkey!)
const statusEvent = statusEvents.find(event => getTagValue("d", event.tags) === address)
const statusTags = statusEvent
? parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, statusEvent.content))
: []
const {status = "error", message = "Your alert was not activated"}: Record<string, string> =
fromPairs(statusTags)
if (status === "error") {
return {error: message}
}
return {ok: true}
}
export const createDmAlert = async () => {
if (!shouldUnwrap.get()) {
shouldUnwrap.set(true)
}
const $pubkey = pubkey.get()!
return createAlert({
description: `for direct messages.`,
feed: makeIntersectionFeed(
feedFromFilters([{kinds: [WRAP], "#p": [$pubkey]}]),
makeRelayFeed(...getPubkeyRelays($pubkey, RelayMode.Messaging)),
),
})
}
// Settings
export const makeSettings = async (params: Partial<SettingsValues>) => {
+10 -70
View File
@@ -51,13 +51,7 @@ import {
deriveEventsByIdForUrl,
getEventsByIdForUrl,
} from "@welshman/store"
import {isKindFeed, findFeed} from "@welshman/feeds"
import {
ALERT_ANDROID,
ALERT_EMAIL,
ALERT_IOS,
ALERT_STATUS,
ALERT_WEB,
APP_DATA,
CLIENT_AUTH,
COMMENT,
@@ -94,7 +88,6 @@ import {
getListTags,
getPubkeyTagValues,
getRelayTagValues,
getTagValue,
getTagValues,
isRelayUrl,
normalizeRelayUrl,
@@ -105,7 +98,6 @@ import {
ManagementMethod,
} from "@welshman/util"
import type {TrustedEvent, RelayProfile, PublishedRoomMeta, List, Filter} from "@welshman/util"
import {decrypt} from "@welshman/signer"
import {routerContext, Router} from "@welshman/router"
import {
pubkey,
@@ -114,7 +106,6 @@ import {
createSearch,
userFollowList,
ensurePlaintext,
signer,
makeOutboxLoader,
appContext,
deriveRelay,
@@ -283,8 +274,11 @@ export type SettingsValues = {
relay_auth: RelayAuthMode
send_delay: number
font_size: number
play_notification_sound: boolean
show_notifications_badge: boolean
alerts_spaces: boolean
alerts_mentions: boolean
alerts_messages: boolean
alerts_sound: boolean
alerts_badge: boolean
}
export type Settings = {
@@ -301,8 +295,11 @@ export const defaultSettings: SettingsValues = {
relay_auth: RelayAuthMode.Conservative,
send_delay: 0,
font_size: 1.1,
play_notification_sound: true,
show_notifications_badge: true,
alerts_spaces: true,
alerts_mentions: true,
alerts_messages: true,
alerts_sound: true,
alerts_badge: true,
}
export const settingsByPubkey = deriveItemsByKey({
@@ -341,63 +338,6 @@ export const relaysPendingTrust = writable<string[]>([])
export const relaysMostlyRestricted = writable<Record<string, string>>({})
// Alerts
export type Alert = {
event: TrustedEvent
tags: string[][]
}
export const alertsById = deriveItemsByKey<Alert>({
repository,
getKey: alert => alert.event.id,
filters: [{kinds: [ALERT_EMAIL, ALERT_WEB, ALERT_IOS, ALERT_ANDROID]}],
eventToItem: async event => {
const $signer = signer.get()
if ($signer) {
const tags = parseJson(await decrypt($signer, NOTIFIER_PUBKEY, event.content))
return {event, tags}
}
},
})
export const getAlertFeed = (alert: Alert) =>
tryCatch(() => JSON.parse(getTagValue("feed", alert.tags)!))
export const dmAlert = derived(alertsById, $alertsById => {
for (const alert of $alertsById.values()) {
if (findFeed(getAlertFeed(alert), f => isKindFeed(f) && f.includes(WRAP))) {
return alert
}
}
})
// Alert Statuses
export type AlertStatus = {
event: TrustedEvent
tags: string[][]
}
export const alertStatusesByAddress = deriveItemsByKey<AlertStatus>({
repository,
filters: [{kinds: [ALERT_STATUS]}],
getKey: alertStatus => getTagValue("d", alertStatus.event.tags)!,
eventToItem: async event => {
const $signer = signer.get()
if ($signer) {
const tags = parseJson(await decrypt($signer, NOTIFIER_PUBKEY, event.content))
return {event, tags}
}
},
})
export const deriveAlertStatus = makeDeriveItem(alertStatusesByAddress)
// Chats
export type Chat = {