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", "relayList", "relayListStatus", "profileStatusNip05", "profileStatusLud16" ] static values = { userAddress: String, pubkeyHex: String, site: String, sharedSecret: String } connect () { if (window.nostr) { 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") } } async setPubkey () { this.setPubkeyTarget.disabled = true try { // Auth based on NIP-42 const signedEvent = await window.nostr.signEvent({ created_at: Math.floor(Date.now() / 1000), kind: 22242, tags: [ ["site", this.siteValue], ["challenge", this.sharedSecretValue] ], content: "" }) const res = await fetch("/settings/set_nostr_pubkey", { method: "POST", credentials: "include", headers: { "Accept": "application/json", 'Content-Type': 'application/json', "X-CSRF-Token": this.csrfToken }, body: JSON.stringify({ signed_event: signedEvent }) }); window.location.reload() } catch (error) { console.warn('Unable to verify pubkey:', error.message) this.setPubkeyTarget.disabled = false } } 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')) } }