UNPKG

nostr-web-components

Version:

collection of web components that provide quick access to basic nostr things

139 lines (138 loc) 4.84 kB
import { decodeNostrURI } from "@nostr/tools/nip19"; import { normalizeURL } from "@nostr/tools/utils"; import { getInboxRelaysFor, getOutboxRelaysFor, getWindowNostr, pool } from "./nostr.js"; import { debounce } from "./utils.js"; class NostrRSVP extends HTMLElement { static observedAttributes = ["ref"]; root; eventData = null; constructor() { super(); this.attachShadow({ mode: "open" }); const { shadowRoot } = this; this.root = document.createElement("div"); shadowRoot.appendChild(this.root); } connectedCallback() { this.style.display = "block"; this.style.width = "fit-content"; this.style.height = "fit-content"; this.set(); } attributeChangedCallback() { this.set(); } set = debounce(async () => { let input = this.getAttribute("ref"); if (!input) return; let { type, data } = decodeNostrURI(input); let relays = []; let author; let filter; if (type === "nevent") { let d = data; relays = d.relays || []; author = d.author; filter = { ids: [d.id] }; } else if (type === "naddr") { let d = data; relays = d.relays || []; author = d.pubkey; filter = { authors: [d.pubkey], kinds: [d.kind], "#d": [d.identifier] }; } else if (input.match(/[0-9a-f]{64}/)) { filter = { ids: [input] }; } else { return; } relays = relays.map(normalizeURL); if (author) { let authorRelays = await getOutboxRelaysFor(author); relays.push(...authorRelays); } let evt = await pool.get(relays, filter); if (!evt || evt.kind !== 31922 && evt.kind !== 31923) { this.root.innerHTML = '<p part="error">Not a valid calendar event.</p>'; return; } this.eventData = evt; const title = evt.tags.find((t) => t[0] === "title")?.[1] || "Untitled Event"; let timeStr = ""; if (evt.kind === 31922) { const start = evt.tags.find((t) => t[0] === "start")?.[1]; const end = evt.tags.find((t) => t[0] === "end")?.[1] || start; timeStr = `${start}${end !== start ? ` to ${end}` : ""}`; } else { const start = evt.tags.find((t) => t[0] === "start")?.[1]; const end = evt.tags.find((t) => t[0] === "end")?.[1]; if (start) { const startDate = new Date(parseInt(start) * 1e3); timeStr = startDate.toLocaleString(); if (end) { const endDate = new Date(parseInt(end) * 1e3); timeStr += ` to ${endDate.toLocaleString()}`; } } } const identifier = evt.tags.find((t) => t[0] === "d")?.[1] || ""; this.root.innerHTML = ` <div part="container"> <p part="title" title="${identifier}">${title}</p> <p part="time">${timeStr}</p> <p part="description">${evt.content}</p> <div part="buttons"> <button part="button accepted"><slot name="accepted">Going</slot></button> <button part="button tentative"><slot name="tentative">Maybe</slot></button> <button part="button declined"><slot name="declined">Not going</slot></button> </div> </div> `; const buttons = this.root.querySelectorAll("button"); buttons[0].addEventListener("click", () => this.sendRSVP("accepted")); buttons[1].addEventListener("click", () => this.sendRSVP("tentative")); buttons[2].addEventListener("click", () => this.sendRSVP("declined")); }, 200); async sendRSVP(status) { if (!this.eventData) { console.error("Cannot RSVP: missing event data"); return; } try { const signedEvent = await (await getWindowNostr()).signEvent({ kind: 31925, created_at: Math.floor(Date.now() / 1e3), content: "", tags: [ ["e", this.eventData.id], [ "a", `${this.eventData.kind}:${this.eventData.pubkey}:${this.eventData.tags.find((t) => t[0] === "d")?.[1]}` ], ["d", Math.random().toString().substring(8)], ["status", status], ["p", this.eventData.pubkey] ] }); const pubs = pool.publish(await getInboxRelaysFor(this.eventData.pubkey), signedEvent); await Promise.all(pubs); const buttonsDiv = this.root.querySelector('[part="buttons"]'); if (buttonsDiv) { buttonsDiv.innerHTML = `<p part="rsvp-sent"><slot name="rsvp-sent">RSVP sent: ${status}</slot></p>`; } } catch (error) { console.error("failed to send RSVP:", error); const buttonsDiv = this.root.querySelector('[part="buttons"]'); if (buttonsDiv) { buttonsDiv.innerHTML = `<p part="rsvp-failed"><slot name="rsvp-failed">failed to send RSVP: ${error}</slot></p>`; } } } } window.customElements.define("nostr-rsvp", NostrRSVP); export { NostrRSVP as default };