Support auth-protected images

This commit is contained in:
Jon Staab
2025-04-28 15:46:48 -07:00
parent ccdd18a863
commit b5a28c71ad
6 changed files with 62 additions and 8 deletions
+1 -1
View File
@@ -141,7 +141,7 @@
<ContentToken value={parsed.value} />
{:else if isLink(parsed)}
{#if isBlock(i)}
<ContentLinkBlock value={parsed.value} />
<ContentLinkBlock value={parsed.value} {event} />
{:else}
<ContentLinkInline value={parsed.value} />
{/if}
+3 -2
View File
@@ -4,9 +4,10 @@
import {preventDefault, stopPropagation} from "@lib/html"
import Link from "@lib/components/Link.svelte"
import ContentLinkDetail from "@app/components/ContentLinkDetail.svelte"
import ContentLinkBlockImage from "@app/components/ContentLinkBlockImage.svelte"
import {pushModal} from "@app/modal"
const {value} = $props()
const {value, event} = $props()
let hideImage = $state(false)
@@ -37,7 +38,7 @@
</video>
{:else if url.match(/\.(jpe?g|png|gif|webp)$/)}
<button type="button" onclick={stopPropagation(preventDefault(expand))}>
<img alt="Link preview" src={imgproxy(url)} class="m-auto max-h-96 rounded-box" />
<ContentLinkBlockImage {event} {value} />
</button>
{:else}
{#await loadPreview()}
@@ -0,0 +1,49 @@
<script lang="ts">
import {onDestroy} from "svelte"
import {now} from "@welshman/lib"
import {BLOSSOM_AUTH, makeEvent, getTags, getTagValue, tagsFromIMeta} from "@welshman/util"
import {signer} from "@welshman/app"
import {imgproxy} from "@app/state"
const {value, event} = $props()
const url = value.url.toString()
// If we fail to fetch the image, try authenticating if we have a blossom hash
const onerror = async () => {
const meta = getTags("imeta", event.tags)
.map(tagsFromIMeta)
.find(meta => getTagValue("url", meta) === url)
const hash = meta ? getTagValue("x", meta) : undefined
if (hash && $signer) {
const event = await signer.get().sign(
makeEvent(BLOSSOM_AUTH, {
tags: [
["t", "get"],
["x", hash],
["expiration", String(now() + 30)],
],
}),
)
const res = await fetch(url, {
headers: {
Authorization: `Nostr ${btoa(JSON.stringify(event))}`,
},
})
if (res.status === 200) {
src = URL.createObjectURL(await res.blob())
}
}
}
let src = $state(imgproxy(url))
onDestroy(() => {
URL.revokeObjectURL(src)
})
</script>
<img alt="" {src} {onerror} class="m-auto max-h-96 rounded-box" />
+4 -1
View File
@@ -41,6 +41,7 @@ import {
loadMutes,
loadFollows,
loadProfile,
loadRelaySelections,
loadInboxRelaySelections,
} from "@welshman/app"
import {createScroller} from "@lib/html"
@@ -384,7 +385,9 @@ export const listenForNotifications = () => {
return () => controller.abort()
}
export const loadUserData = (pubkey: string, relays: string[] = []) => {
export const loadUserData = async (pubkey: string, relays: string[] = []) => {
await Promise.race([sleep(3000), loadRelaySelections(pubkey, relays)])
const promise = Promise.race([
sleep(3000),
Promise.all([
+1 -1
View File
@@ -115,7 +115,7 @@ export const IMGPROXY_URL = "https://imgproxy.coracle.social"
export const REACTION_KINDS = [REACTION, ZAP_RESPONSE]
export const NIP46_PERMS =
"nip04_encrypt,nip04_decrypt,nip44_encrypt,nip44_decrypt," +
"nip44_encrypt,nip44_decrypt," +
[CLIENT_AUTH, AUTH_JOIN, MESSAGE, THREAD, COMMENT, GROUPS, WRAP, REACTION]
.map(k => `sign_event:${k}`)
.join(",")
+4 -3
View File
@@ -18,16 +18,17 @@
const onsubmit = preventDefault(async () => {
const json = JSON.stringify($state.snapshot(settings))
const content = await $signer!.nip04.encrypt($pubkey!, json)
const content = await $signer!.nip44.encrypt($pubkey!, json)
const relays = Router.get().FromUser().getUrls()
publishThunk({
event: createEvent(SETTINGS, {content}),
relays: Router.get().FromUser().getUrls(),
relays,
})
publishThunk({
event: createEvent(MUTES, {tags: mutedPubkeys.map(tagPubkey)}),
relays: Router.get().FromUser().getUrls(),
relays,
})
pushToast({message: "Your settings have been saved!"})