235 lines
6.2 KiB
JavaScript
235 lines
6.2 KiB
JavaScript
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 => `
|
|
// <li class="flex items-center justify-between p-2 border-b">
|
|
// <span>${relay.url}</span>
|
|
// <button
|
|
// data-action="click->list#handleItemClick"
|
|
// data-item="${relay.url}"
|
|
// class="bg-blue-500 text-white px-3 py-1 rounded">
|
|
// Action
|
|
// </button>
|
|
// </li>
|
|
// `).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'))
|
|
}
|
|
}
|