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 => ` //