nostr-web-components
Version:
collection of web components that provide quick access to basic nostr things
116 lines (115 loc) • 4.01 kB
JavaScript
import { getOutboxRelaysFor, getWindowNostr, inputToPubkey, pool } from "./nostr.js";
class NostrFollow extends HTMLElement {
static observedAttributes = ["pubkey"];
button;
slotDefault;
slotLoading;
slotSuccess;
slotFailure;
constructor() {
super();
this.attachShadow({ mode: "open" });
const { shadowRoot } = this;
this.button = document.createElement("button");
this.button.setAttribute("part", "button");
this.button.onclick = this.handleClick.bind(this);
this.slotDefault = document.createElement("slot");
this.slotDefault.textContent = "Follow me on Nostr!";
this.button.appendChild(this.slotDefault);
this.slotLoading = document.createElement("slot");
this.slotLoading.name = "loading";
this.slotLoading.style.display = "none";
this.slotLoading.textContent = "Following...";
this.button.appendChild(this.slotLoading);
this.slotSuccess = document.createElement("slot");
this.slotSuccess.name = "success";
this.slotSuccess.style.display = "none";
this.slotSuccess.textContent = "Followed.";
this.button.appendChild(this.slotSuccess);
this.slotFailure = document.createElement("slot");
this.slotFailure.name = "failure";
this.slotFailure.style.display = "none";
this.slotFailure.textContent = "Error: ";
this.button.appendChild(this.slotFailure);
const errorMessage = document.createElement("span");
errorMessage.setAttribute("part", "error-message");
this.slotFailure.appendChild(errorMessage);
shadowRoot.appendChild(this.button);
}
*queryPart(name) {
let slotted = this.button.querySelectorAll(`[part="${name}"]`);
for (let i = 0; i < slotted.length; i++) {
yield slotted[i];
}
let slotted2 = this.querySelectorAll(`[part="${name}"]`);
for (let i = 0; i < slotted2.length; i++) {
yield slotted2[i];
}
}
async handleClick() {
const [target, hints] = await inputToPubkey(this.getAttribute("pubkey"));
if (!target)
return;
this.button.onclick = null;
this.button.disabled = true;
this.slotLoading.style.display = "";
this.slotDefault.style.display = "none";
try {
const publicKey = await (await getWindowNostr()).getPublicKey();
const relays = await getOutboxRelaysFor(publicKey);
relays.push(...hints);
const res = await pool.querySync(relays, {
kinds: [3],
authors: [publicKey]
});
if (res.length === 0) {
throw new Error("couldn't find your contact list");
}
const event = res[0];
if (event.tags.find((tag) => tag[1] === target)) {
this.slotLoading.style.display = "none";
this.slotSuccess.style.display = "";
this.dispatchEvent(
new CustomEvent("success", {
bubbles: true,
detail: { already: true }
})
);
return;
}
event.tags.push(["p", target, relays[0]]);
event.created_at = Math.round(Date.now() / 1e3);
const signedEvent = await (await getWindowNostr()).signEvent(event);
await Promise.any(pool.publish(relays, signedEvent));
this.slotLoading.style.display = "none";
this.slotSuccess.style.display = "";
this.dispatchEvent(
new CustomEvent("success", {
bubbles: true,
detail: { already: false }
})
);
} catch (error) {
console.warn("failed to follow:", error);
this.slotLoading.style.display = "none";
this.slotFailure.style.display = "";
this.dispatchEvent(
new CustomEvent("failure", {
bubbles: true,
detail: { message: String(error) }
})
);
for (let el of this.queryPart("error-message")) {
let msg = String(error);
if (msg.startsWith("Error:")) {
msg = msg.substring(6);
}
el.textContent = msg.trim();
}
}
}
}
window.customElements.define("nostr-follow", NostrFollow);
export {
NostrFollow as default
};