diff --git a/app/components/settings/nostr_edit_profile_component.rb b/app/components/settings/nostr_edit_profile_component.rb index 6231caa..edf040f 100644 --- a/app/components/settings/nostr_edit_profile_component.rb +++ b/app/components/settings/nostr_edit_profile_component.rb @@ -3,25 +3,14 @@ module Settings class NostrEditProfileComponent < ViewComponent::Base def initialize(user:, profile_event:) - if profile_event.present? - @user = user - @profile_event = profile_event - @profile = JSON.parse(profile_event["content"]) + @user = user + @profile_event = profile_event + @profile = {} + @display_name = nil + + if profile_event.present? && profile_event["content"].present? + @profile = JSON.parse(profile_event["content"]) rescue {} @display_name = @profile["display_name"] || @profile["displayName"] - - if @profile["nip05"].present? && @profile["nip05"] == @user.address - # "Your profile's Nostr address is set to #{ user_address }" - else - # "Your profile's Nostr address is not set to #{ user_address } yet" - end - - if @profile["lud16"].present? && @profile["lud16"] == @user.address - # "Your profile's Lightning address is set to #{ user_address }" - else - # "Your profile's Lightning address is not set to #{ user_address } yet" - end - else - # "We could not find a profile for your public key" end end end diff --git a/app/components/settings/nostr_relay_status_component.rb b/app/components/settings/nostr_relay_status_component.rb index 88d5d6d..783ea70 100644 --- a/app/components/settings/nostr_relay_status_component.rb +++ b/app/components/settings/nostr_relay_status_component.rb @@ -2,9 +2,11 @@ module Settings class NostrRelayStatusComponent < ViewComponent::Base - def initialize(nip65_event:) + def initialize(nip65_event:, relay_url: Setting.nostr_relay_url) + @relay_url = relay_url + if nip65_event.present? - if relay_urls(nip65_event).any? { |r| r.include?("wss://nostr.kosmos.org") } + if relay_urls(nip65_event).include?(@relay_url) @text = "You have a relay list, and the Kosmos relay is part of it" @icon_name = "check-circle" @icon_color = "emerald-500" diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index 13d3d47..e980efb 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -9,7 +9,7 @@ class SettingsController < ApplicationController ] before_action :set_user, only: [ :show, :update, :update_email, :reset_email_password, - :fetch_nostr_user_metadata + :fetch_nostr_metadata ] def index @@ -148,26 +148,45 @@ class SettingsController < ApplicationController } end - def fetch_nostr_user_metadata - if @user.nostr_pubkey.present? - outbox_relay_urls = nil + def fetch_nostr_metadata + return render plain: "no pubkey", status: :bad_request if @user.nostr_pubkey.blank? - # if @nip65_event = NostrManager::DiscoverUserRelays.call(pubkey: @user.nostr_pubkey) - # relay_tags = @nip65_event["tags"].select{ |t| t[0] == "r" } - # outbox_relay_urls = relay_tags&.select{ |t| t[2] != "read" }&.map{ |t| t[1] } - # end + cache_key = "nostr_metadata:#{@user.id}" + + case params[:step] + when "relays" + nip65_event = NostrManager::DiscoverUserRelays.call(pubkey: @user.nostr_pubkey) + write_relays = nip65_event ? extract_write_relays(nip65_event) : [] + + if nip65_event && write_relays.any? && stale?(nip65_event) + auth_event = NostrManager::DiscoverUserRelays.call( + pubkey: @user.nostr_pubkey, relays: write_relays + ) + nip65_event = auth_event if auth_event + write_relays = extract_write_relays(nip65_event) if auth_event + end + + Rails.cache.write("#{cache_key}:nip65", nip65_event, expires_in: 5.minutes) + Rails.cache.write("#{cache_key}:write_relays", write_relays, expires_in: 5.minutes) + render json: { next: "profile" } + + when "profile" + write_relays = Rails.cache.read("#{cache_key}:write_relays") || [] + relays = write_relays.any? ? write_relays : nil + profile = NostrManager::DiscoverUserProfile.call( + pubkey: @user.nostr_pubkey, relays: relays + ) + Rails.cache.write("#{cache_key}:profile", profile, expires_in: 5.minutes) + render json: { next: "render" } + + when "render", nil + @nip65_event = Rails.cache.read("#{cache_key}:nip65") + @profile = Rails.cache.read("#{cache_key}:profile") + render partial: "nostr_metadata", formats: :html - # @profile = NostrManager::DiscoverUserProfile.call( - # pubkey: @user.nostr_pubkey, - # relays: outbox_relay_urls - # ) - @profile = {"content"=>"{\"name\":\"jimmy\",\"picture\":\"https://storage.kosmos.org/jimmy/public/shares/241028-1117-tony.jpg\",\"banner\":\"https://storage.kosmos.org/raucao/public/shares/240604-1517-1500x500.jpg\",\"nip05\":\"jimmy@kosmos.org\",\"lud16\":\"jimmy@kosmos.org\",\"pubkey\":\"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3\",\"display_name\":\"Jimmy\",\"displayName\":\"Jimmy\",\"about\":\"I don't exist. Follow at your own peril.\"}", "created_at"=>1730114246, "id"=>"6b15b1308a61ee837bd3b50319978314650e435891c259f4ea499f819f35a4f6", "kind"=>0, "pubkey"=>"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3", "sig"=>"4f681f4b95646bbf88a6eae9ca92c0f2ce5effecfa017556a23490f91a99243aedf81d956ee2466ed64fecb9a03b6b89cd80ff116df0178830977e203867d7ae", "tags"=>[]} - # @profile = {"content"=>"{\"name\":\"jimmy\",\"nip05\":\"jimmy@kosmos.org\",\"lud16\":\"jimmy@kosmos.org\",\"pubkey\":\"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3\",\"display_name\":\"Jimmy\",\"displayName\":\"Jimmy\",\"about\":\"I don't exist. Follow at your own peril.\"}", "created_at"=>1730114246, "id"=>"6b15b1308a61ee837bd3b50319978314650e435891c259f4ea499f819f35a4f6", "kind"=>0, "pubkey"=>"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3", "sig"=>"4f681f4b95646bbf88a6eae9ca92c0f2ce5effecfa017556a23490f91a99243aedf81d956ee2466ed64fecb9a03b6b89cd80ff116df0178830977e203867d7ae", "tags"=>[]} else - @relays, @profile = [nil, nil] + render plain: "invalid step", status: :bad_request end - - render partial: 'nostr_user_metadata' end private @@ -176,6 +195,16 @@ class SettingsController < ApplicationController @current_section = :settings end + def extract_write_relays(nip65_event) + nip65_event["tags"] + .select { |t| t[0] == "r" && t[2] != "read" } + .map { |t| t[1] } + end + + def stale?(event, threshold = 30.minutes) + Time.at(event["created_at"]) < threshold.ago + end + def set_settings_section @settings_section = params[:section] allowed_sections = [ diff --git a/app/javascript/controllers/nostr_metadata_controller.js b/app/javascript/controllers/nostr_metadata_controller.js new file mode 100644 index 0000000..c6dedd1 --- /dev/null +++ b/app/javascript/controllers/nostr_metadata_controller.js @@ -0,0 +1,50 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = [ "status", "loading", "content" ] + static values = { url: String } + + connect () { + this.runStep("relays") + } + + async runStep (step) { + const messages = { + relays: "Looking up your relay list…", + profile: "Fetching your profile…", + blossom: "Fetching your media server list…", + render: null + } + + if (messages[step]) { + this.statusTarget.textContent = messages[step] + } + + try { + const headers = (step === "render") + ? { "Accept": "text/html" } + : { "Accept": "application/json" } + + const res = await fetch(`${this.urlValue}?step=${step}`, { headers }) + + if (!res.ok) { + throw new Error(`HTTP ${res.status}`) + } + + if (step === "render") { + const html = await res.text() + this.loadingTarget.classList.add("hidden") + this.contentTarget.classList.remove("hidden") + this.contentTarget.innerHTML = html + } else { + const data = await res.json() + if (data.next) { + this.runStep(data.next) + } + } + } catch (error) { + console.warn("Nostr metadata fetch failed:", error.message) + this.statusTarget.textContent = "Something went wrong. Please reload." + } + } +} diff --git a/app/services/nostr_manager/discover_user_relays.rb b/app/services/nostr_manager/discover_user_relays.rb index 213eaf7..e4579f1 100644 --- a/app/services/nostr_manager/discover_user_relays.rb +++ b/app/services/nostr_manager/discover_user_relays.rb @@ -1,8 +1,8 @@ module NostrManager class DiscoverUserRelays < NostrManagerService - def initialize(pubkey:) + def initialize(pubkey:, relays: nil) @pubkey = pubkey - @relays = Setting.nostr_discovery_relays + @relays = relays.present? ? relays : Setting.nostr_discovery_relays end def call diff --git a/app/views/settings/_nostr.html.erb b/app/views/settings/_nostr.html.erb index 1949fc3..c915b3c 100644 --- a/app/views/settings/_nostr.html.erb +++ b/app/views/settings/_nostr.html.erb @@ -88,8 +88,15 @@ <% if current_user.nostr_pubkey.present? %> - <%= turbo_frame_tag "nostr_user_metadata", src: nostr_user_metadata_settings_path do %> -

Loading...

- <% end %> +
+
+
+

Loading…

+
+ +
<% end %> diff --git a/app/views/settings/_nostr_metadata.html.erb b/app/views/settings/_nostr_metadata.html.erb new file mode 100644 index 0000000..47841c2 --- /dev/null +++ b/app/views/settings/_nostr_metadata.html.erb @@ -0,0 +1,25 @@ +
+

Profile

+ <%= render Settings::NostrProfileStatusComponent.new( + profile_event: @profile, + user_address: current_user.address + ) %> +
+ + <%= render ModalComponent.new(show_close_button: false) do %> + <%= render Settings::NostrEditProfileComponent.new( + user: current_user, + profile_event: @profile + ) %> + <% end %> +
+
+ +
+

Relays

+ <%= render Settings::NostrRelayStatusComponent.new( + nip65_event: @nip65_event + ) %> +
diff --git a/app/views/settings/_nostr_user_metadata.html.erb b/app/views/settings/_nostr_user_metadata.html.erb deleted file mode 100644 index 9db7cec..0000000 --- a/app/views/settings/_nostr_user_metadata.html.erb +++ /dev/null @@ -1,27 +0,0 @@ -<%= turbo_frame_tag "nostr_user_metadata" do %> -
-

Relays

- <%= render Settings::NostrRelayStatusComponent.new( - nip65_event: @nip65_event - ) %> -
- -
-

Profile

- <%= render Settings::NostrProfileStatusComponent.new( - profile_event: @profile, - user_address: current_user.address - ) %> -
- - <%= render ModalComponent.new(show_close_button: false) do %> - <%= render Settings::NostrEditProfileComponent.new( - user: current_user, - profile_event: @profile - ) %> - <% end %> -
-
-<% end %> diff --git a/config/routes.rb b/config/routes.rb index 712fd7e..a29c670 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -68,7 +68,7 @@ Rails.application.routes.draw do post 'reset_email_password' post 'set_nostr_pubkey' delete 'nostr_pubkey', to: 'settings#remove_nostr_pubkey' - get 'fetch_nostr_user_metadata', as: 'nostr_user_metadata' + get 'fetch_nostr_metadata', as: 'nostr_metadata' end end