Skip to content

Conversation

@alltheseas
Copy link
Collaborator

@alltheseas alltheseas commented Dec 15, 2025

Summary

Add Blossom media server upload support, allowing users to upload images and videos to their preferred Blossom server instead of nostr.build.

Screenshots

Screenshot 2025-12-15 at 3 25 55 PM image image

Key features:

  • Core Blossom client with kind 24242 authorization (BUD-01, BUD-02)
  • Server list model for kind 10063 events (BUD-03)
  • Settings UI for configuring Blossom servers
  • Integration with existing media upload flow

Key differences from NIP-96 uploaders:

Aspect NIP-96 (nostr.build) Blossom
Auth event kind 27235 24242
Upload method POST multipart form-data PUT raw binary body
Auth tags ["u", url], ["method", method] ["t", "upload"], ["x", sha256]

Checklist

Standard PR Checklist

  • I have read (or I am familiar with) the Contribution Guidelines
  • I have tested the changes in this PR
  • I have profiled the changes to ensure there are no performance regressions, or I do not need to profile the changes.
    • If not needed, provide reason: New feature with minimal impact on existing code paths; upload only triggered when user explicitly selects Blossom uploader
  • I have opened or referred to an existing github issue related to this change: Add Blossom media server option #3218
  • My PR is either small, or I have split it into smaller logical commits that are easier to review
  • I have added the signoff line to all my commits. See Signing off your work
  • I have added appropriate changelog entries for the changes in this PR. See Adding changelog entries
  • I have added appropriate Closes: or Fixes: tags in the commit messages wherever applicable, or made sure those are not needed. See Submitting patches

Test report

Device: iPhone 17 (Xcode Simulator)

iOS: iOS 26

Damus: Branch add-blossom-upload (commit 70b7ece)

Setup:

  • Selected "Blossom" as default media uploader in Settings > Media Servers
  • Configured Blossom server URLs for testing

Steps:

  1. Navigate to Settings > Media Servers
  2. Select "Blossom" as the default uploader
  3. Add server URL (tested with https://blossom.primal.net and https://cdn.satellite.earth)
  4. Create a new post and attach an image
  5. Publish the post
  6. Verify the image URL in the published note points to the Blossom server

Results:

  • PASS
    • Successfully uploaded media to Primal Blossom server [no auth flow]
    • Successfully uploaded media to Satellite.earth Blossom server [auth flow]
    • Existing nostr.build uploader continues to work

Other notes

New files:

  • damus/Core/Blossom/BlossomTypes.swift - Server URL wrapper, blob descriptor, error types
  • damus/Core/Blossom/BlossomAuth.swift - Kind 24242 authorization event creation
  • damus/Core/Blossom/BlossomUploader.swift - PUT /upload with raw binary body
  • damus/Core/Blossom/BlossomServerList.swift - Kind 10063 server list model
  • damus/Core/Blossom/BlossomServerListManager.swift - Server list state management
  • damus/Features/Settings/Views/MediaServerSettingsView.swift - Main settings view
  • damus/Features/Settings/Views/AddBlossomServerView.swift - Add/edit server sheet

Modified files:

  • damus/Shared/Media/Images/AttachMediaUtility.swift - Added Blossom upload branch
  • damus/Shared/Media/Models/MediaUploader.swift - Added .blossom case
  • damus/Core/Nostr/NostrKind.swift - Added blossom_auth (24242), blossom_server_list (10063)
  • damus/Features/Settings/Views/ConfigView.swift - Added Media Servers navigation
  • damus/Shared/Utilities/Router.swift - Added MediaServerSettings route

Closes #3218

alltheseas and others added 4 commits December 15, 2025 15:11
Implements foundational Blossom protocol support (BUD-01, BUD-02) for
uploading blobs to Blossom media servers.

Key components:
- BlossomTypes.swift: Server URL wrapper, blob descriptor, error types
- BlossomAuth.swift: Kind 24242 authorization event creation
  (differs from NIP-98 by using ["t", action], ["x", sha256], ["expiration"])
- BlossomUploader.swift: PUT /upload with raw binary body

Unlike NIP-96 uploaders (nostr.build), Blossom uses:
- PUT method instead of POST
- Raw binary body instead of multipart form-data
- Kind 24242 auth instead of NIP-98 kind 27235
- SHA256 hash of file required for authorization

Also adds NostrKind cases for blossom_auth (24242) and
blossom_server_list (10063).

Closes: damus-io#3218 (partial)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: alltheseas <[email protected]>
Implements BUD-03 user server list support for tracking user's
preferred Blossom media servers.

New files:
- BlossomServerList.swift: Model for kind 10063 events
  - Parses ["server", url] tags from events
  - Converts to NostrEvent for publishing
  - Supports adding/removing servers immutably

- BlossomServerListManager.swift: Manages server list state
  - Loads from NostrDB or manual settings
  - Publishes updates to relays
  - Follows UserRelayListManager pattern

Settings additions:
- latestBlossomServerListEventIdHex: Event ID for NostrDB lookup
- manualBlossomServerUrl: Primary method for v1 - direct URL entry
  without requiring kind 10063 event publishing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: alltheseas <[email protected]>
Adds Settings UI for configuring Blossom media servers.

New views:
- MediaServerSettingsView: Main settings view for media upload servers
  - Default uploader picker (nostr.build, nostrcheck, Blossom)
  - Blossom server configuration section (visible when Blossom selected)
  - Current server display, change/remove server buttons
- AddBlossomServerView: Sheet for entering/editing Blossom server URL
  - URL validation with error feedback
  - Paste from clipboard support
  - Public server examples for quick setup

MediaUploader changes:
- Added .blossom case to enum
- Blossom uses kind 24242 auth (not NIP-98)
- Server URL retrieved from settings at upload time

Integration:
- Router: Added MediaServerSettings route
- ConfigView: Added "Media Servers" navigation link
- AppearanceSettingsView: Shows server status/warning when Blossom selected

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Signed-off-by: alltheseas <[email protected]>
Connects BlossomUploader to the existing media upload infrastructure
so users can actually upload images and videos via Blossom.

Key changes to AttachMediaUtility:
- Added uploadViaBlossom() helper method that:
  - Gets server URL from UserSettingsStore.shared
  - Validates keypair availability
  - Loads media data and calls BlossomUploader
  - Maps BlossomUploadResult to ImageUploadResult
- Modified create_upload_request() to branch early for Blossom
  - Checks if uploader is .blossom via type cast
  - Calls uploadViaBlossom() instead of NIP-96 flow

Also adds Blossom core files (Types, Auth, Uploader) to the
DamusNotificationService target to resolve build errors.

Changelog-Added: Added Blossom media server upload support
Closes: damus-io#3218
Signed-off-by: alltheseas <[email protected]>
@alltheseas alltheseas requested a review from tyiu December 15, 2025 21:20
@alltheseas
Copy link
Collaborator Author

@fishcakeday can you review ser

@alltheseas
Copy link
Collaborator Author

@lovvtide I tested the auth blossom upload flow via iOS Damus to satellite blossom server successfully.

Any feedback on this blossom upload PR for iOS Damus?

@danieldaquino danieldaquino added the pr-in-queue This PR is waiting in a queue behind their other PRs marked with the label `pr-active-review`. label Dec 15, 2025
@alltheseas
Copy link
Collaborator Author

@BenGWeeks 👀

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for uploading media to Blossom servers as an alternative to the existing nostr.build and nostrcheck uploaders. The implementation includes a complete Blossom client (BUD-01/BUD-02), settings UI for server configuration, and integration with the existing media upload flow. The initial version uses manually configured server URLs stored in settings, with kind 10063 server list event support (BUD-03) planned for a future release.

Key changes:

  • Core Blossom client implementation with kind 24242 authorization, PUT-based uploads with raw binary bodies, and SHA256 hash-based authentication
  • Settings UI for selecting Blossom as the default uploader and configuring server URLs
  • Integration with existing AttachMediaUtility that branches to Blossom upload for the new uploader type

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
damus/Core/Blossom/BlossomTypes.swift Defines core types: BlossomServerURL wrapper with validation, BlossomBlobDescriptor response model, error types, and upload result enum
damus/Core/Blossom/BlossomAuth.swift Implements kind 24242 authorization event creation with required tags (t, x, expiration) and base64 encoding for HTTP headers
damus/Core/Blossom/BlossomUploader.swift Implements PUT /upload endpoint with raw binary body, SHA256 computation, and blob descriptor response parsing
damus/Core/Blossom/BlossomServerList.swift Models kind 10063 server list events with parsing from NostrEvent/NdbNote and conversion to publishable events
damus/Core/Blossom/BlossomServerListManager.swift Manager class for fetching, storing, and publishing server lists (follows UserRelayListManager pattern but not yet instantiated)
damus/Features/Settings/Views/MediaServerSettingsView.swift Main settings view for selecting default uploader and configuring Blossom server URL
damus/Features/Settings/Views/AddBlossomServerView.swift Sheet for adding/editing Blossom server URLs with validation, clipboard paste, and example servers
damus/Features/Settings/Views/ConfigView.swift Adds Media Servers navigation link to settings
damus/Features/Settings/Views/AppearanceSettingsView.swift Updates uploader picker to show Blossom server status and configuration warnings
damus/Shared/Media/Images/AttachMediaUtility.swift Adds uploadViaBlossom function and early branching logic to route Blossom uploads separately from NIP-96 uploads
damus/Shared/Media/Models/MediaUploader.swift Adds .blossom case with appropriate property values (empty nameParam, no NIP-98 requirement, empty postAPI)
damus/Shared/Utilities/Router.swift Adds MediaServerSettings route for navigation
damus/Features/Settings/Models/UserSettingsStore.swift Adds Blossom-specific settings: latestBlossomServerListEventIdHex and manualBlossomServerUrl
damus/Core/Nostr/NostrKind.swift Adds blossom_auth (24242) and blossom_server_list (10063) event kinds
damus/Features/Timeline/Models/HomeModel.swift Adds cases for blossom_auth and blossom_server_list in event processing switch
damus/Features/Events/Models/LoadableNostrEventView.swift Adds Blossom kinds to unsupported event types list
damus.xcodeproj/project.pbxproj Adds new Blossom source files to build targets

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@alltheseas
Copy link
Collaborator Author

Addressing the comments, and noting that claude takes exception with copilots diagnosis, and proposed solutions 🙂

Critical Review of Copilot Feedback                                                                                                                             
                                                                                                                                                                  
  Comment 1 & 4: "Lacks test coverage" (BlossomUploader and BlossomAuth)                                                                                          
                                                                                                                                                                  
  Verdict: Weak argument, low priority                                                                                                                            
                                                                                                                                                                  
  Copilot claims the project has "comprehensive test coverage for other components" - this is inaccurate. I checked:                                              
                                                                                                                                                                  
  - NIP-98 auth (Nip98HTTPAuth.swift:10-23) - the existing equivalent to BlossomAuth - has zero tests                                                             
  - Media uploaders (nostr.build, nostrcheck) - the existing equivalent to BlossomUploader - have zero tests                                                      
  - EditPictureControlTests.swift uses mocks that completely bypass actual upload logic                                                                           
                                                                                                                                                                  
  The Blossom code is consistent with the project's existing testing approach. Adding tests is always nice, but singling out Blossom for lacking tests when the   
  equivalent NIP-96 code has the same gap is cherry-picking.                                                                                                      
                                                                                                                                                                  
  ---                                                                                                                                                             
  Comment 2: "BlossomServerListManager is not instantiated"                                                                                                       
                                                                                                                                                                  
  Verdict: Partially valid, easy fix                                                                                                                              
                                                                                                                                                                  
  The comment in HomeModel.swift:237 says events are "handled separately by BlossomServerListManager" - Copilot correctly notes this is premature since the       
  manager isn't wired up yet.                                                                                                                                     
                                                                                                                                                                  
  However, the BlossomServerListManager class does exist and is fully implemented (BlossomServerListManager.swift). The PR description clearly states kind 10063  
  support is "planned for a future release."                                                                                                                      
                                                                                                                                                                  
  Suggested fix: Update the comment to:                                                                                                                           
  break   // Blossom events (kind 10063) are not currently processed; server list support planned for future release                                              
                                                                                                                                                                  
  This is more accurate than Copilot's suggestion which incorrectly says "kind 10063-based Blossom server lists" when it should reference the current v1 approach 
  uses manual settings.                                                                                                                                           
                                                                                                                                                                  
  ---                                                                                                                                                             
  Comment 3: Progress delegate not used for Blossom                                                                                                               
                                                                                                                                                                  
  Verdict: Valid concern, but Copilot's fix is wrong                                                                                                              
                                                                                                                                                                  
  This is the most actionable feedback. The progress parameter is indeed ignored in the Blossom path, while NIP-96 uploads use it (URLSession.shared.data(for:    
  request, delegate: progress)).                                                                                                                                  
                                                                                                                                                                  
  However, Copilot's suggested fix is terrible - it creates a wrapper function that accepts progress but still ignores it internally. This is deceptive           
  indirection that makes the code worse, not better.                                                                                                              
                                                                                                                                                                  
  Proper fix would be to either:                                                                                                                                  
  1. Actually use the progress delegate with URLSession.shared.data(for:delegate:) in BlossomUploader.upload                                                      
  2. Or document why progress tracking isn't supported (e.g., if Blossom uploads are typically small/fast)                                                        
                                                                                                                                                                  
  ---                                                                                                                                                             
  Summary                                                                                                                                                         
                                                                                                                                                                  
  | Feedback                          | Priority | Action                                        |                                                                
  |-----------------------------------|----------|-----------------------------------------------|                                                                
  | Test coverage for BlossomUploader | Low      | Defer - matches existing pattern              |                                                                
  | Test coverage for BlossomAuth     | Low      | Defer - matches existing pattern              |                                                                
  | Misleading comment                | Low      | Quick fix - clarify comment                   |                                                                
  | Progress delegate unused          | Medium   | Actually implement or document the limitation |   

alltheseas and others added 3 commits December 17, 2025 23:51
Update comment to accurately reflect that kind 10063 server list
support is planned for future release, and v1 uses manual server
URL in settings.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Thread URLSessionTaskDelegate through Blossom upload path to enable
upload progress tracking, matching the existing NIP-96 upload behavior.

Changes:
- Add progressDelegate parameter to BlossomUploader.upload()
- Pass delegate to URLSession.shared.data(for:delegate:)
- Update AttachMediaUtility to forward progress delegate

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add comprehensive unit tests for Blossom authentication and upload
functionality (32 tests total).

BlossomAuthTests (11 tests):
- Kind 24242 event creation and signing
- Required tags (t, x, expiration)
- Base64 encoding and header format
- Custom expiration support

BlossomUploaderTests (21 tests):
- BlossomServerURL validation and endpoints
- BlossomBlobDescriptor JSON parsing
- Error descriptions
- Upload result handling
- Hashable/Equatable/Codable conformance

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@alltheseas
Copy link
Collaborator Author

@jb55 comments are addressed

Copy link
Contributor

@fishcakeday fishcakeday left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a look, but did not do in depth analyses. Looking good so far.

@alltheseas
Copy link
Collaborator Author

Thanks @fishcakeday, investigating. Your feedback reminded me to test out video support.

- Add streaming SHA256 computation to avoid loading entire file into memory
- Add file-based upload using URLSession.upload(fromFile:) for large files
- Fix processVideo() missing return statement that caused video uploads to fail
- Fix MediaPicker to copy videos immediately before temp file is deleted
- Auto-select streaming upload for files >1MB, in-memory for smaller files
- Add unit tests for streaming SHA256

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@alltheseas
Copy link
Collaborator Author

Video support works on my local build: https://damus.io/nevent1qqsz9x638y7d8gquk8kv22zr54hntm8af5kd38qg0ca2z4e0q4m5szsvn6cq9

{"content":"Satellite blossom vid upload test\n\nhttps:\/\/cdn.satellite.earth\/13d90a9351926c33a670b401b8fa2444d23edbca90f454eac958da59e20a792f.mp4","tags":[["imeta","url https:\/\/cdn.satellite.earth\/13d90a9351926c33a670b401b8fa2444d23edbca90f454eac958da59e20a792f.mp4","blurhash eA68EXof00M{_3j[fQayfQj[00WB~qt74nayfQj[fQay_3j[4nWB?b","dim 240x180"],["r","https:\/\/cdn.satellite.earth\/13d90a9351926c33a670b401b8fa2444d23edbca90f454eac958da59e20a792f.mp4"]],"id":"229b51393cd3a01cb1ecc52843a56f35ecfd4d2cd89c087e3aa1572f0577480a","kind":1,"pubkey":"dd50ea8a48412a15621be2804ffed3b27eb2e8f2005dadc88baa077b6739d856","sig":"6fe202aec0fad498ad15238b7aed0deb5a065c7dec4c7ba23b72af3e8158c9c4d6983b43e00527026d6d51e5f13c82901dd1ebdce6f731bf647c48ce186732ec","created_at":1766075925}

alltheseas and others added 4 commits December 18, 2025 11:24
nostr.download (Route96) is a Blossom-compatible media server
that implements BUD-01 through BUD-09. Added to the suggested
servers list for easier user onboarding.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Implements blob mirroring per BUD-04 specification for redundancy:

BlossomUploader.swift:
- Add mirror() function implementing PUT /mirror endpoint
- Add mirrorToServersInBackground() for fire-and-forget mirroring
- Server downloads blob from source URL (more efficient than re-upload)

UserSettingsStore.swift:
- Add blossomMirrorEnabled toggle setting
- Add blossomMirrorServers array for configured backup servers
- JSON encoding for array storage in UserDefaults

AttachMediaUtility.swift:
- Integrate mirror triggering after successful primary upload
- Extract triggerBackgroundMirroring() helper with guard clauses
- Mirroring runs in background, doesn't block upload response

Flow: Upload to primary → Return URL → Background mirror to backups

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
MediaServerSettingsView.swift:
- Add "Backup Mirrors" section with enable toggle
- List configured mirror servers with swipe-to-delete
- Add "Publish Server List" section for BUD-03 compliance

AddBlossomMirrorServerView:
- Sheet for adding mirror server URLs
- Suggested servers list (primal, nostr.download, oxtr, satellite)
- Validation: prevents duplicates and adding primary as mirror
- Early returns with guard clauses for clean control flow

Kind 10063 publication:
- publishServerList() creates event with ordered server tags
- Primary server first, then mirrors in configured order
- Broadcasts via postbox to all connected relays
- Caches event ID locally for retrieval

This enables other clients to discover user's blob servers
when media is unavailable from the original URL.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- HTTPS-only: BlossomServerURL now rejects HTTP URLs for security
- Fix tests to expect HTTP rejection (testServerURLRejectsHttp)
- Fix testUploadWithInvalidServerURL to use HTTPS
- Update UI error messages to say "https" only (not "http or https")
- Add @mainactor to setManualServer() for concurrency safety
- Add /media endpoint property for BUD-05 media optimization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@alltheseas
Copy link
Collaborator Author

alltheseas commented Dec 18, 2025

@fishcakeday latest commits should address your feedback.

added blossom mirroring @BenGWeeks

Screenshot 2025-12-18 at 11 48 30 AM

@alltheseas
Copy link
Collaborator Author

alltheseas commented Dec 18, 2025

screenshot

Screenshot 2025-12-18 at 1 33 25 PM

Kind 10063 Server List Publication (BUD-03)

When the user taps "Publish to Relays" in Media Server settings:

  1. Build server list - Primary Blossom server first, then any configured mirror servers in order
  2. Create kind 10063 event - Replaceable event with server tags containing each server URL
  3. Sign with user's keypair - Standard nostr event signing
  4. Broadcast to relays - Sent via the existing postbox to all connected relays
  5. Cache event ID locally - Stored in settings for future reference

This allows other nostr clients to discover the user's preferred Blossom servers if their primary server is unavailable, enabling blob retrieval from mirrors.

Kind 10063 Event Structure:
{
  "kind": 10063,
  "content": "",
  "tags": [
    ["server", "https://primary.blossom.server"],
    ["server", "https://mirror1.blossom.server"],
    ["server", "https://mirror2.blossom.server"]
  ]
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-in-queue This PR is waiting in a queue behind their other PRs marked with the label `pr-active-review`.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Blossom media server option

3 participants