mirror of
https://github.com/lumina-rocks/lumina.git
synced 2026-06-06 10:41:20 +02:00
BUD-03 implementation (WIP)
This commit is contained in:
76
.github/prompts/blossom-bud03.prompt.md
vendored
Normal file
76
.github/prompts/blossom-bud03.prompt.md
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# BUD-03
|
||||
|
||||
## User Server List
|
||||
|
||||
`draft` `optional`
|
||||
|
||||
Defines a replaceable event using `kind:10063` to advertise the blossom servers a user uses to host their blobs.
|
||||
|
||||
The event MUST include at least one `server` tag containing the full server URL including the `http://` or `https://`.
|
||||
|
||||
The order of these tags is important and should be arranged with the users most "reliable" or "trusted" servers being first.
|
||||
|
||||
The `.content` field is not used.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "e4bee088334cb5d38cff1616e964369c37b6081be997962ab289d6c671975d71",
|
||||
"pubkey": "781208004e09102d7da3b7345e64fd193cd1bc3fce8fdae6008d77f9cabcd036",
|
||||
"content": "",
|
||||
"kind": 10063,
|
||||
"created_at": 1708774162,
|
||||
"tags": [
|
||||
["server", "https://cdn.self.hosted"],
|
||||
["server", "https://cdn.satellite.earth"]
|
||||
],
|
||||
"sig": "cc5efa74f59e80622c77cacf4dd62076bcb7581b45e9acff471e7963a1f4d8b3406adab5ee1ac9673487480e57d20e523428e60ffcc7e7a904ac882cfccfc653"
|
||||
}
|
||||
```
|
||||
|
||||
## Client Upload Implementation
|
||||
|
||||
When uploading blobs clients MUST attempt to upload the blob to at least the first `server` listed in the users server list.
|
||||
|
||||
Optionally clients MAY upload the blob to all the servers or mirror the blob to the other servers if they support [BUD-04](./04.md)
|
||||
|
||||
This ensures that the blob is available in multiple locations in the case one of the servers goes offline.
|
||||
|
||||
## Client Retrieval Implementation
|
||||
|
||||
When extracting the SHA256 hash from the URL clients MUST use the last occurrence of a 64 char hex string. This allows clients to extract hashes from blossom URLs and SOME non-blossom URLs.
|
||||
|
||||
In all the following examples, the hash `b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553` should be selected
|
||||
|
||||
- Blossom URLs
|
||||
- `https://blossom.example.com/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf`
|
||||
- `https://cdn.example.com/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553`
|
||||
- Non Blossom URLs
|
||||
- `https://cdn.example.com/user/ec4425ff5e9446080d2f70440188e3ca5d6da8713db7bdeef73d0ed54d9093f0/media/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf`
|
||||
- `https://cdn.example.com/media/user-name/documents/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf`
|
||||
- `http://download.example.com/downloads/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553`
|
||||
- `http://media.example.com/documents/b1/67/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf`
|
||||
|
||||
In the context of nostr events, clients SHOULD use the author's server list when looking for blobs that are no longer available at the original URL.
|
||||
|
||||
Take the following event as an example
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "834185269f4ab72539193105060dbb1c8b2efd702d14481cea345c47beefe6eb",
|
||||
"pubkey": "ec4425ff5e9446080d2f70440188e3ca5d6da8713db7bdeef73d0ed54d9093f0",
|
||||
"content": "I've developed a new open source P2P e-cash system called Bitcoin. check it out\nhttps://cdn.broken-domain.com/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf",
|
||||
"kind": 1,
|
||||
"created_at": 1297484820,
|
||||
"tags": [],
|
||||
"sig": "bd4bb200bdd5f7ffe5dbc3e539052e27b05d6f9f528e255b1bc4261cc16b8f2ad85c89eef990c5f2eee756ef71b4c571ecf6a88ad12f7338e321dd60c6a903b5"
|
||||
}
|
||||
```
|
||||
|
||||
Once the client discovers that the URL `https://cdn.broken-domain.com/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf` is no longer available. It can perform the following steps to find the blob:
|
||||
|
||||
1. Get the SHA256 has from the URL
|
||||
2. Look for the authors server list `kind:10063`
|
||||
3. If found, Attempt to retrieve the blob from each `server` listed started with the first
|
||||
4. If not found, the client MAY fallback to using a well-known popular blossom server to retrieve the blob
|
||||
|
||||
This ensures clients can quickly find missing blobs using the users list of trusted servers.
|
||||
@@ -22,6 +22,7 @@ import { signEvent } from "@/utils/utils"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { toast } from "./ui/use-toast"
|
||||
|
||||
async function calculateBlurhash(file: File): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -58,6 +59,77 @@ const UploadComponent: React.FC = () => {
|
||||
const [shouldFetch, setShouldFetch] = useState(false)
|
||||
const [serverChoice, setServerChoice] = useState("blossom.band")
|
||||
const [enableNip89, setEnableNip89] = useState(false)
|
||||
|
||||
// New state for user Blossom servers
|
||||
const [userBlossomServers, setUserBlossomServers] = useState<string[]>([])
|
||||
const [loadingBlossomServers, setLoadingBlossomServers] = useState(false)
|
||||
|
||||
// Get the user's pubkey
|
||||
const userPubkey = typeof window !== "undefined" ? window.localStorage.getItem("pubkey") || "" : ""
|
||||
|
||||
// Fetch user's Blossom servers from kind:10063 events
|
||||
const { events: blossomServerEvents } = useNostrEvents({
|
||||
filter: {
|
||||
authors: userPubkey ? [userPubkey] : [],
|
||||
kinds: [10063],
|
||||
limit: 1,
|
||||
},
|
||||
enabled: !!userPubkey,
|
||||
})
|
||||
|
||||
// Function to load user's Blossom servers
|
||||
const loadUserBlossomServers = useCallback(() => {
|
||||
setLoadingBlossomServers(true)
|
||||
|
||||
if (blossomServerEvents.length > 0) {
|
||||
// Extract server URLs from server tags
|
||||
const servers = blossomServerEvents[0].tags
|
||||
.filter(tag => tag[0] === 'server')
|
||||
.map(tag => {
|
||||
// Extract hostname from URL
|
||||
try {
|
||||
const url = new URL(tag[1])
|
||||
return url.hostname
|
||||
} catch (e) {
|
||||
console.error("Invalid server URL:", tag[1])
|
||||
return null
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as string[]
|
||||
|
||||
setUserBlossomServers(servers)
|
||||
|
||||
// Set the first server as the selected server if available
|
||||
if (servers.length > 0) {
|
||||
setServerChoice(servers[0])
|
||||
toast({
|
||||
title: "Blossom Servers Loaded",
|
||||
description: `Loaded ${servers.length} Blossom server(s) from your kind:10063 event.`,
|
||||
})
|
||||
} else {
|
||||
toast({
|
||||
title: "No Blossom Servers Found",
|
||||
description: "Your kind:10063 event doesn't contain any valid server tags.",
|
||||
variant: "destructive"
|
||||
})
|
||||
}
|
||||
} else {
|
||||
toast({
|
||||
title: "No Blossom Servers Found",
|
||||
description: "You don't have a kind:10063 event configured with Blossom servers.",
|
||||
variant: "destructive"
|
||||
})
|
||||
}
|
||||
|
||||
setLoadingBlossomServers(false)
|
||||
}, [blossomServerEvents, setServerChoice])
|
||||
|
||||
// Effect to detect when Blossom server events are loaded
|
||||
useEffect(() => {
|
||||
if (blossomServerEvents.length > 0) {
|
||||
console.log("Found kind:10063 Blossom server event:", blossomServerEvents[0])
|
||||
}
|
||||
}, [blossomServerEvents])
|
||||
|
||||
const { events, isLoading: isNoteLoading } = useNostrEvents({
|
||||
filter: shouldFetch
|
||||
@@ -300,19 +372,44 @@ const UploadComponent: React.FC = () => {
|
||||
<div className="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input id="file" name="file" type="file" accept="image/*" onChange={handleFileChange} />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 w-full max-w-sm items-center gap-1.5">
|
||||
{/* <select value={serverChoice} onChange={handleServerChange} className="w-full">
|
||||
<option value="nostr.download">nostr.download</option>
|
||||
<option value="blossom.primal.net">blossom.primal.net</option>
|
||||
</select> */}
|
||||
Upload to
|
||||
|
||||
<div className="grid w-full max-w-sm items-center gap-1.5">
|
||||
<div className="flex items-center justify-between space-x-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={loadUserBlossomServers}
|
||||
disabled={loadingBlossomServers || !userPubkey}
|
||||
className="text-xs"
|
||||
>
|
||||
{loadingBlossomServers ? (
|
||||
<>Loading Servers <ReloadIcon className="ml-2 h-3 w-3 animate-spin" /></>
|
||||
) : (
|
||||
"Load My Blossom Servers"
|
||||
)}
|
||||
</Button>
|
||||
<Label>Upload to:</Label>
|
||||
</div>
|
||||
|
||||
<Select onValueChange={handleServerChange} value={serverChoice}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={serverChoice} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{/* Default servers */}
|
||||
<SelectItem value="blossom.band">blossom.band</SelectItem>
|
||||
<SelectItem value="media.lumina.rocks">media.lumina.rocks</SelectItem>
|
||||
|
||||
{/* User's Blossom servers */}
|
||||
{userBlossomServers.length > 0 && (
|
||||
<>
|
||||
{userBlossomServers.map((server) => (
|
||||
<SelectItem key={server} value={server}>
|
||||
{server} (Your Server)
|
||||
</SelectItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user