Files
codium_nostr_extension/plans/blossom-upload.md
T

161 lines
8.1 KiB
Markdown

# 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
```mermaid
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
```markdown
---
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.