Files

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:

  1. Quick upload — upload the active editor file to a Blossom server and broadcast a kind 1063 metadata event with sensible defaults.
  2. 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 /upload with binary body. Request headers: Content-Type, Content-Length, optional X-SHA-256 (lowercase hex). Response: a blob descriptor { url, sha256, size, type, uploaded, nip94? }.
  • BUD-03 — User server list = replaceable kind 10063 event with server tags (full URLs, ordered by trust). Clients MUST upload to at least the first listed server.
  • BUD-08 — Servers MAY return a nip94 field: 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 t verb (upload/get/list/delete/media), expiration tag, optional server and x (sha256) tags. Sent as Authorization: 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
...
  • file key 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)

  1. Require sign-in (same gate as long-form publish).
  2. Parse Blossom front-matter from the active .md file.
  3. Resolve the file to upload: file key, else the active file on disk. For an untitled buffer, upload the editor text (UTF-8) with text/markdown mime.
  4. Compute sha256 + size + mime (sniff from extension, fallback application/octet-stream).
  5. 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.
  6. Confirmation dialog: show title, author, size, mime, sha256 (short), the checked Blossom servers (toggleable), and the target relays for the 1063 event. Confirm → proceed.
  7. For each checked server: build a kind 24242 upload auth token scoped to the server domain + sha256, PUT /upload, parse descriptor. Upload runs concurrently across all checked servers (upload-to-all-checked by default).
  8. Build the kind 1063 event from the first successful descriptor + front-matter (the descriptor's url/x/m/size are canonical; additional successful uploads are listed in the results).
  9. Sign with the active NostrSigner.
  10. Publish the kind 1063 event to the enabled Nostr relays via publishToRelays.
  11. 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-matter summary (empty string if absent), so clients that only render content still show a description.