8.1 KiB
8.1 KiB
Blossom Server Upload — Implementation Plan
Goal
Add the ability to upload files (especially long documents/books) to Blossom media servers and publish the associated NIP-94 file-metadata notes (kind 1063) to Nostr relays. Two entry points:
- Quick upload — upload the active editor file to a Blossom server and broadcast a kind 1063 metadata event with sensible defaults.
- Header-based authoring — a markdown file with YAML front-matter that carries all metadata needed to build the associated notes; publishing uploads the file body to Blossom and broadcasts the kind 1063 event.
Protocol summary (from ~/lt/blossom)
- BUD-01 —
GET/HEAD /<sha256>retrieval. Servers served from domain root. - BUD-02 —
PUT /uploadwith binary body. Request headers:Content-Type,Content-Length, optionalX-SHA-256(lowercase hex). Response: a blob descriptor{ url, sha256, size, type, uploaded, nip94? }. - BUD-03 — User server list = replaceable kind 10063 event with
servertags (full URLs, ordered by trust). Clients MUST upload to at least the first listed server. - BUD-08 — Servers MAY return a
nip94field: a JSON array of NIP-94 KV tags (url,m,x,size,magnet,i, …) to embed in a kind 1063 event. - BUD-11 — Authorization: a signed kind 24242 event with
tverb (upload/get/list/delete/media),expirationtag, optionalserverandx(sha256) tags. Sent asAuthorization: Nostr <base64url-no-pad(event)>.
Note on server-list kind: Blossom uses kind 10063 (BUD-03). NIP-96 uses kind 10096. We fetch 10063 (Blossom-native) on sign-in and also check 10096 as a fallback, since some servers/clients publish one or both.
Architecture
flowchart LR
A[Active .md file + front-matter] --> B[Parse Blossom front-matter]
B --> C[Read file bytes + sha256 + mime]
C --> D[Build kind 24242 upload auth token]
D --> E[PUT /upload to Blossom server]
E --> F[Blob descriptor: url, sha256, size, type, nip94?]
F --> G[Build kind 1063 NIP-94 event]
G --> H[Sign with NostrSigner]
H --> I[Publish kind 1063 to relays]
New modules
| File | Responsibility |
|---|---|
src/blossom/client.ts |
HTTP client: uploadBlob, listBlobs, deleteBlob, mirrorBlob. Uses node:https/node:http. Builds the PUT /upload request with headers + binary body, parses the blob descriptor. |
src/blossom/auth.ts |
buildBlossomAuthToken(signer, { verb, server?, sha256?, expirationSec }) → signed kind 24242 event. toAuthorizationHeader(event) → Nostr <base64url>. |
src/blossom/serverList.ts |
fetchBlossomServers(pubkey, seedRelays) → queries kind 10063 (and 10096 fallback), returns ordered server URLs. parseBlossomServers(event). Mirrors fetchUserRelays in relay.ts. |
src/nostr/nip94.ts |
buildFileMetadataEvent({ descriptor, meta, publishedAt }) → unsigned kind 1063 EventTemplate. Merges server-returned nip94 tags with front-matter-derived tags (title, summary, t, alt, published_at, author, language, year, image). |
src/blossom/frontmatter.ts |
Parse Blossom-specific front-matter keys: file (path to upload, defaults to the active file), author, language, year, cover (image URL), plus reuse slug/title/summary/tags. Reuses the YAML parser in frontmatter.ts. |
Modified modules
| File | Change |
|---|---|
src/config.ts |
Add blossomServers: string[] (manual override), blossomUploadTimeoutMs, blossomAuthTokenTtlSec. |
src/state.ts |
Store discovered Blossom servers in memory (like relays); expose getBlossomServers()/setBlossomServers(). |
src/extension.ts |
New commands: nostr.uploadToBlossom (quick), nostr.publishBlossomFile (header-based), nostr.fetchBlossomServers. On sign-in, fetch kind 10063 alongside relays. Reuse getSigner, publishToRelays, confirmation-dialog pattern. |
src/ui/sidebar.ts |
New Blossom section: server list (toggle), Upload File button, Publish to Blossom button. New WebviewMessage variants + SidebarState.blossomServers. |
package.json |
Register commands, activation events, config properties, editor/context menus. |
Blossom front-matter format
---
slug: narrative-of-frederick-douglass
title: Narrative of the Life of Frederick Douglass
summary: An 1845 autobiography by Frederick Douglass.
author: Frederick Douglass
language: en
year: 1845
cover: https://cdn.example.com/douglass-cover.png
tags: [history, abolition, autobiography]
---
# Narrative of the Life of Frederick Douglass
...
filekey is optional; if omitted, the active editor's file is uploaded.- The front-matter block is stripped from the uploaded blob (the book body is uploaded clean), matching the long-form publish behaviour.
- Unknown keys are ignored (forward-compatible).
Kind 1063 event shape
kind: 1063
tags:
- ["url", <blob url>]
- ["m", <mime type>]
- ["x", <sha256 hex>]
- ["size", <bytes>]
- ["title", <from front-matter>]
- ["summary", <from front-matter>]
- ["image", <cover url>] (if present)
- ["author", <author>] (if present)
- ["language", <lang>] (if present)
- ["year", <year>] (if present)
- ["published_at", <unix>]
- ["t", <tag>]... (from front-matter tags)
- ["alt", "File: <title>"]
content: "" (or a short description / the summary)
Server-returned nip94 tags take precedence for url/m/x/size; the
extension adds the descriptive metadata tags.
Upload flow (header-based)
- Require sign-in (same gate as long-form publish).
- Parse Blossom front-matter from the active
.mdfile. - Resolve the file to upload:
filekey, else the active file on disk. For an untitled buffer, upload the editor text (UTF-8) withtext/markdownmime. - Compute sha256 + size + mime (sniff from extension, fallback
application/octet-stream). - Resolve target Blossom servers: the sidebar Blossom Servers list with
checkboxes (mirroring the Relays section). Every checked server receives
the upload. Source order: sidebar override → state (fetched 10063) → config
nostr.blossomServers. Must have ≥1 checked. - Confirmation dialog: show title, author, size, mime, sha256 (short), the checked Blossom servers (toggleable), and the target relays for the 1063 event. Confirm → proceed.
- For each checked server: build a kind 24242
uploadauth token scoped to the server domain + sha256,PUT /upload, parse descriptor. Upload runs concurrently across all checked servers (upload-to-all-checked by default). - Build the kind 1063 event from the first successful descriptor + front-matter
(the descriptor's
url/x/m/sizeare canonical; additional successful uploads are listed in the results). - Sign with the active
NostrSigner. - Publish the kind 1063 event to the enabled Nostr relays via
publishToRelays. - Show results (blob URL, servers, relay results) in the output channel + information message. Offer to copy the blob URL to clipboard.
Quick upload flow
Same as above but with no front-matter required: derive title from filename
stem, summary empty, no author/language/year/cover, tags empty. Uploads the
active file directly. Still broadcasts a kind 1063 event.
Decisions
- Server selection: The sidebar shows a Blossom Servers list with checkboxes, exactly like the Relays section. Every checked server receives the upload (upload-to-all-checked by default). The confirmation dialog also shows the checked servers and lets the user toggle before proceeding. This mirrors the existing relay UX.
- Auth token TTL: default 60 seconds (configurable).
- MIME sniffing: map common extensions (
.md→text/markdown,.pdf→application/pdf,.png,.jpg,.epub→application/epub+zip, …). No dependency on a mime-db package; small inline table. - Kind 1063
content: carries the front-mattersummary(empty string if absent), so clients that only rendercontentstill show a description.