diff --git a/app/javascript/controllers/settings/nostr_pubkey_controller.js b/app/javascript/controllers/settings/nostr_pubkey_controller.js index 4de30e5..038d7cd 100644 --- a/app/javascript/controllers/settings/nostr_pubkey_controller.js +++ b/app/javascript/controllers/settings/nostr_pubkey_controller.js @@ -1,8 +1,14 @@ import { Controller } from "@hotwired/stimulus" +import { Nostrify } from "nostrify" // Connects to data-controller="settings--nostr-pubkey" export default class extends Controller { - static targets = [ "noExtension", "setPubkey", "pubkeyBech32Input" ] + static targets = [ + "noExtension", + "setPubkey", "pubkeyBech32Input", + "relayList", "relayListStatus", + "profileStatusNip05", "profileStatusLud16" + ] static values = { userAddress: String, pubkeyHex: String, @@ -15,6 +21,14 @@ export default class extends Controller { if (this.hasSetPubkeyTarget) { this.setPubkeyTarget.disabled = false } + + if (this.pubkeyHexValue) { + this.discoverUserOnNostr().then(() => { + this.renderRelayStatus() + this.renderProfileNip05Status() + this.renderProfileLud16Status() + }) + } } else { this.noExtensionTarget.classList.remove("hidden") } @@ -49,8 +63,172 @@ export default class extends Controller { } } + async discoverUserOnNostr () { + this.nip65Relays = await this.findUserRelays() + this.profile = await this.findUserProfile() + } + + async findUserRelays () { + const controller = new AbortController(); + const signal = controller.signal; + const filters = [{ kinds: [10002], authors: [this.pubkeyHexValue], limit: 1 }] + const messages = [] + + for await (const msg of this.discoveryPool.req(filters, { signal })) { + if (msg[0] === 'EVENT') { + if (!messages.find(m => m.id === msg[2].id)) { + messages.push(msg[2]) + } + } + if (msg[0] === 'EOSE') { break } + } + + // Close the relay subscription + controller.abort() + if (messages.length === 0) { return messages } + + const sortedMessages = messages.sort((a, b) => a.createdAt - b.createdAt) + const newestMessage = messages[messages.length - 1] + + return newestMessage.tags.filter(t => t[0] === 'r') + .map(t => { return { url: t[1], marker: t[2] } }) + } + + async findUserProfile () { + const controller = new AbortController(); + const signal = controller.signal; + const filters = [{ kinds: [0], authors: [this.pubkeyHexValue], limit: 1 }] + const messages = [] + + for await (const msg of this.discoveryPool.req(filters, { signal })) { + if (msg[0] === 'EVENT') { + if (!messages.find(m => m.id === msg[2].id)) { + messages.push(msg[2]) + } + } + if (msg[0] === 'EOSE') { break } + } + + // Close the relay subscription + controller.abort() + if (messages.length === 0) { return null } + + const sortedMessages = messages.sort((a, b) => a.createdAt - b.createdAt) + const newestMessage = messages[messages.length - 1] + + return JSON.parse(newestMessage.content) + } + + renderRelayStatus () { + let showStatus + + if (this.nip65Relays.length > 0) { + if (this.relaysContainAccountsRelay) { + showStatus = 'green' + } else { + showStatus = 'orange' + } + } else { + showStatus = 'red' + } + // showStatus = 'red' + + this.relayListStatusTarget + .querySelector(`.status-${showStatus}`) + .classList.remove("hidden") + } + + renderProfileNip05Status () { + let showStatus + + if (this.profile?.nip05) { + if (this.profile.nip05 === this.userAddressValue) { + showStatus = 'green' + } else { + showStatus = 'red' + } + } else { + showStatus = 'orange' + } + + this.profileStatusNip05Target + .querySelector(`.status-${showStatus}`) + .classList.remove("hidden") + } + + renderProfileLud16Status () { + let showStatus + + if (this.profile?.lud16) { + if (this.profile.lud16 === this.userAddressValue) { + showStatus = 'green' + } else { + showStatus = 'red' + } + } else { + showStatus = 'orange' + } + + this.profileStatusLud16Target + .querySelector(`.status-${showStatus}`) + .classList.remove("hidden") + } + + // renderRelayList (relays) { + // const html = relays.map(relay => ` + //
  • + // ${relay.url} + // + //
  • + // `).join("") + // + // this.relayListTarget.innerHTML = html + // } + get csrfToken () { const element = document.head.querySelector('meta[name="csrf-token"]') return element.getAttribute("content") } + + // Used to find a user's profile and relays + get discoveryRelays () { + return [ + 'ws://localhost:4777', + 'wss://nostr.kosmos.org', + 'wss://purplepag.es', + // 'wss://relay.nostr.band', + // 'wss://njump.me', + // 'wss://relay.damus.io', + // 'wss://nos.lol', + // 'wss://eden.nostr.land', + // 'wss://relay.snort.social', + // 'wss://nostr.wine', + // 'wss://relay.primal.net', + // 'wss://nostr.bitcoiner.social', + ] + } + + get discoveryPool () { + if (!this._discoveryPool) { + this._discoveryPool = new Nostrify.NPool({ + open: (url) => new Nostrify.NRelay1(url), + reqRouter: async (filters) => new Map( + this.discoveryRelays.map(relayUrl => [ relayUrl, filters ]) + ), + eventRouter: async (event) => [], + }) + } + + return this._discoveryPool + } + + get relaysContainAccountsRelay () { + // TODO use URL from view/settings + return !!this.nip65Relays.find(r => r.url.match('wss://nostr.kosmos.org')) + } } diff --git a/app/views/settings/_nostr.html.erb b/app/views/settings/_nostr.html.erb index 1251dd5..7579a69 100644 --- a/app/views/settings/_nostr.html.erb +++ b/app/views/settings/_nostr.html.erb @@ -1,46 +1,32 @@ -
    -

    Nostr

    -

    Public Key

    -
    - -

    +

    +
    +

    Nostr

    +

    + Public Key +

    +

    + name="nostr_public_key" class="w-full" /> <%= link_to nostr_pubkey_settings_path, - class: 'btn-md btn-outline text-red-700 relative shrink-0', + class: 'btn-md btn-outline relative grow-0 shrink-0 text-red-700', data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } do %> Remove <% end %>

    <% if current_user.nostr_pubkey.present? %> -
    -
    -
    - -
    -
    -

    - Your user address <%= current_user.address %> is - also a Nostr address now. Use your favorite Nostr app, or for - example metadata.nostr.com, to add this - NIP-05 address to your public profile. -

    -
    -
    -
    + + + <% else %>

    - If you use any apps on the Nostr network, you can verify your public key - with us in order to enable Nostr-specific features for your account. + Verify your Nostr public key with us in order to enable Nostr-specific + features for your account.

    <% end %> @@ -58,8 +44,8 @@

    - We recommend Alby, which you can also use for your Lightning - Wallet. + We recommend Alby, which you can also use a wallet for your + Lightning account.

    @@ -86,5 +72,113 @@

    <% end %> -
    -
    +
    + + <% if current_user.nostr_pubkey.present? %> +
    +

    Profile

    +
    + + +
    +
    + + + +
    +
    + + + +
    +
    + +
    +

    Relays

    +
    + + + +
    + +
    + <% end %> +