161 lines
8.1 KiB
Markdown
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.
|