nostr-web-components
Version:
collection of web components that provide quick access to basic nostr things
139 lines (138 loc) • 4.84 kB
JavaScript
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
};