Replace bunker with claim on alerts page

This commit is contained in:
Jon Staab
2025-06-18 17:02:32 -07:00
parent 2fae3ca248
commit 43da7d628e
3 changed files with 83 additions and 122 deletions

View File

@@ -372,12 +372,11 @@ export type AlertParams = {
feed: Feed feed: Feed
cron: string cron: string
email: string email: string
bunker: string
secret: string
description: string description: string
claims: Record<string, string>
} }
export const makeAlert = async ({cron, email, feed, bunker, secret, description}: AlertParams) => { export const makeAlert = async ({cron, email, feed, claims, description}: AlertParams) => {
const tags = [ const tags = [
["feed", JSON.stringify(feed)], ["feed", JSON.stringify(feed)],
["cron", cron], ["cron", cron],
@@ -394,8 +393,8 @@ export const makeAlert = async ({cron, email, feed, bunker, secret, description}
], ],
] ]
if (bunker) { for (const [relay, claim] of Object.entries(claims)) {
tags.push(["nip46", secret, bunker]) tags.push(["claim", relay, claim])
} }
return makeEvent(ALERT, { return makeEvent(ALERT, {

View File

@@ -3,7 +3,6 @@
import {randomInt, displayList, TIMEZONE, identity} from "@welshman/lib" import {randomInt, displayList, TIMEZONE, identity} from "@welshman/lib"
import {displayRelayUrl, getTagValue, THREAD, MESSAGE, EVENT_TIME, COMMENT} from "@welshman/util" import {displayRelayUrl, getTagValue, THREAD, MESSAGE, EVENT_TIME, COMMENT} from "@welshman/util"
import type {Filter} from "@welshman/util" import type {Filter} from "@welshman/util"
import type {Nip46ResponseWithResult} from "@welshman/signer"
import {makeIntersectionFeed, makeRelayFeed, feedFromFilters} from "@welshman/feeds" import {makeIntersectionFeed, makeRelayFeed, feedFromFilters} from "@welshman/feeds"
import {pubkey} from "@welshman/app" import {pubkey} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@@ -12,13 +11,10 @@
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
import InfoBunker from "@app/components/InfoBunker.svelte"
import BunkerConnect, {BunkerConnectController} from "@app/components/BunkerConnect.svelte"
import {alerts, getMembershipUrls, getMembershipRoomsByUrl, userMembership} from "@app/state" import {alerts, getMembershipUrls, getMembershipRoomsByUrl, userMembership} from "@app/state"
import {loadAlertStatuses} from "@app/requests" import {loadAlertStatuses, requestRelayClaims} from "@app/requests"
import {publishAlert} from "@app/commands" import {publishAlert} from "@app/commands"
import {pushToast} from "@app/toast" import {pushToast} from "@app/toast"
import {pushModal} from "@app/modal"
const timezoneOffset = parseInt(TIMEZONE.slice(3)) / 100 const timezoneOffset = parseInt(TIMEZONE.slice(3)) / 100
const minute = randomInt(0, 59) const minute = randomInt(0, 59)
@@ -30,36 +26,12 @@
let cron = WEEKLY let cron = WEEKLY
let email = $alerts.map(a => getTagValue("email", a.tags)).filter(identity)[0] || "" let email = $alerts.map(a => getTagValue("email", a.tags)).filter(identity)[0] || ""
let relay = "" let relay = ""
let bunker = ""
let secret = ""
let notifyThreads = true let notifyThreads = true
let notifyCalendar = true let notifyCalendar = true
let notifyChat = false let notifyChat = false
let showBunker = false
const back = () => history.back() const back = () => history.back()
const controller = new BunkerConnectController({
onNostrConnect: (response: Nip46ResponseWithResult) => {
bunker = controller.broker.getBunkerUrl()
secret = controller.broker.params.clientSecret
showBunker = false
},
})
const connectBunker = () => {
showBunker = true
}
const hideBunker = () => {
showBunker = false
}
const clearBunker = () => {
bunker = ""
secret = ""
}
const submit = async () => { const submit = async () => {
if (!email.includes("@")) { if (!email.includes("@")) {
return pushToast({ return pushToast({
@@ -108,10 +80,11 @@
loading = true loading = true
try { try {
const claims = await requestRelayClaims([relay])
const cadence = cron?.endsWith("1") ? "Weekly" : "Daily" const cadence = cron?.endsWith("1") ? "Weekly" : "Daily"
const description = `${cadence} alert for ${displayList(display)} on ${displayRelayUrl(relay)}, sent via email.` const description = `${cadence} alert for ${displayList(display)} on ${displayRelayUrl(relay)}, sent via email.`
const feed = makeIntersectionFeed(feedFromFilters(filters), makeRelayFeed(relay)) const feed = makeIntersectionFeed(feedFromFilters(filters), makeRelayFeed(relay))
const thunk = await publishAlert({cron, email, feed, bunker, secret, description}) const thunk = await publishAlert({cron, email, feed, claims, description})
await thunk.result await thunk.result
await loadAlertStatuses($pubkey!) await loadAlertStatuses($pubkey!)
@@ -130,13 +103,6 @@
Add an Alert Add an Alert
{/snippet} {/snippet}
</ModalHeader> </ModalHeader>
{#if showBunker}
<div class="card2 flex flex-col items-center gap-4 bg-base-300">
<p>Scan using a nostr signer, or click to copy.</p>
<BunkerConnect {controller} />
<Button class="btn btn-neutral btn-sm" onclick={hideBunker}>Cancel</Button>
</div>
{:else}
<FieldInline> <FieldInline>
{#snippet label()} {#snippet label()}
<p>Email Address*</p> <p>Email Address*</p>
@@ -192,38 +158,12 @@
</div> </div>
{/snippet} {/snippet}
</FieldInline> </FieldInline>
<div class="card2 flex flex-col gap-3 bg-base-300">
<div class="flex items-center justify-between">
<strong>Connect a Bunker</strong>
<span class="flex items-center gap-2 text-sm" class:text-primary={bunker}>
{#if bunker}
<Icon icon="check-circle" size={5} />
Connected
{:else}
<Icon icon="close-circle" size={5} />
Not Connected
{/if}
</span>
</div>
<p class="text-sm">
Required for receiving alerts about spaces with access controls. You can get one from your
<Button class="text-primary" onclick={() => pushModal(InfoBunker)}>remote signer app</Button
>.
</p>
{#if bunker}
<Button class="btn btn-neutral btn-sm flex-grow" onclick={clearBunker}>Disconnect</Button>
{:else}
<Button class="btn btn-primary btn-sm w-full flex-grow" onclick={connectBunker}
>Connect</Button>
{/if}
</div>
{/if}
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon="alt-arrow-left" />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary" disabled={loading || showBunker}> <Button type="submit" class="btn btn-primary" disabled={loading}>
<Spinner {loading}>Confirm</Spinner> <Spinner {loading}>Confirm</Spinner>
<Icon icon="alt-arrow-right" /> <Icon icon="alt-arrow-right" />
</Button> </Button>

View File

@@ -13,12 +13,17 @@ import {
sortBy, sortBy,
assoc, assoc,
now, now,
removeNil,
isNotNil,
filterVals,
fromPairs,
} from "@welshman/lib" } from "@welshman/lib"
import { import {
MESSAGE, MESSAGE,
DELETE, DELETE,
THREAD, THREAD,
EVENT_TIME, EVENT_TIME,
AUTH_INVITE,
COMMENT, COMMENT,
matchFilters, matchFilters,
getTagValues, getTagValues,
@@ -430,3 +435,20 @@ export const discoverRelays = (lists: List[]) =>
.filter(isShareableRelayUrl) .filter(isShareableRelayUrl)
.map(url => loadRelay(url)), .map(url => loadRelay(url)),
) )
export const requestRelayClaim = async (url: string) => {
const relay = await loadRelay(url)
const authors = removeNil([relay?.profile?.self, relay?.profile?.pubkey])
const filters = [{kinds: [AUTH_INVITE], authors, limit: 1}]
const events = await load({filters, relays: [url]})
if (events.length > 0) {
return getTagValue("claim", events[0].tags)
}
}
export const requestRelayClaims = async (urls: string[]) =>
filterVals(
isNotNil,
fromPairs(await Promise.all(urls.map(async url => [url, await requestRelayClaim(url)]))),
)