mirror of
https://github.com/aljazceru/awesome-nostr.git
synced 2026-10-05 11:28:22 +00:00
webln issue when connected
This commit is contained in:
+2
-1
@@ -116,7 +116,8 @@
|
|||||||
</footer>
|
</footer>
|
||||||
<!-- Add marked.js before your script -->
|
<!-- Add marked.js before your script -->
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import 'https://esm.sh/@getalby/bitcoin-connect@3.12.3';
|
import * as bitcoinConnect from 'https://esm.sh/@getalby/bitcoin-connect@3.12.3';
|
||||||
|
window.bitcoinConnect = bitcoinConnect;
|
||||||
</script>
|
</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
<script src="script.js"></script>
|
<script src="script.js"></script>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
const ZAP_SATS = 1000; // default zap amount for chips
|
const ZAP_SATS = 1000; // default zap amount for chips
|
||||||
|
const ZAP_MEMO = 'nostr.net zap'; // so recipients know where it came from
|
||||||
const ZAP_RELAYS = ['wss://relay.nostr.net', 'wss://relay.damus.io', 'wss://nos.lol'];
|
const ZAP_RELAYS = ['wss://relay.nostr.net', 'wss://relay.damus.io', 'wss://nos.lol'];
|
||||||
const NOBLE_URL = 'https://esm.sh/@noble/curves@1.4.0/secp256k1'; // lazy: only for anon zap receipts
|
const NOBLE_URL = 'https://esm.sh/@noble/curves@1.4.0/secp256k1'; // lazy: only for anon zap receipts
|
||||||
const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
|
const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
|
||||||
@@ -78,7 +79,12 @@
|
|||||||
async function fetchInvoice(params, msat, zapEvent) {
|
async function fetchInvoice(params, msat, zapEvent) {
|
||||||
const cb = new URL(params.callback);
|
const cb = new URL(params.callback);
|
||||||
cb.searchParams.set('amount', String(msat));
|
cb.searchParams.set('amount', String(msat));
|
||||||
if (zapEvent) cb.searchParams.set('nostr', JSON.stringify(zapEvent));
|
if (zapEvent) {
|
||||||
|
cb.searchParams.set('nostr', JSON.stringify(zapEvent));
|
||||||
|
} else if (params.commentAllowed) {
|
||||||
|
// plain payment (no zap support): memo via LNURL comment param
|
||||||
|
cb.searchParams.set('comment', ZAP_MEMO.slice(0, params.commentAllowed));
|
||||||
|
}
|
||||||
const r = await fetch(cb);
|
const r = await fetch(cb);
|
||||||
const j = await r.json();
|
const j = await r.json();
|
||||||
if (j.status === 'ERROR') throw new Error(j.reason || 'invoice error');
|
if (j.status === 'ERROR') throw new Error(j.reason || 'invoice error');
|
||||||
@@ -110,7 +116,7 @@
|
|||||||
const pubkey = await window.nostr.getPublicKey();
|
const pubkey = await window.nostr.getPublicKey();
|
||||||
const ev = await window.nostr.signEvent({
|
const ev = await window.nostr.signEvent({
|
||||||
kind: 9734, pubkey, created_at: Math.floor(Date.now() / 1000),
|
kind: 9734, pubkey, created_at: Math.floor(Date.now() / 1000),
|
||||||
tags, content: ''
|
tags, content: ZAP_MEMO
|
||||||
});
|
});
|
||||||
return { event: { ...ev, pubkey: ev.pubkey || pubkey }, anon: false, attributed: true };
|
return { event: { ...ev, pubkey: ev.pubkey || pubkey }, anon: false, attributed: true };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -124,7 +130,7 @@
|
|||||||
const priv = hex(crypto.getRandomValues(new Uint8Array(32)));
|
const priv = hex(crypto.getRandomValues(new Uint8Array(32)));
|
||||||
const pubkey = hex(schnorr.getPublicKey(priv));
|
const pubkey = hex(schnorr.getPublicKey(priv));
|
||||||
const created_at = Math.floor(Date.now() / 1000);
|
const created_at = Math.floor(Date.now() / 1000);
|
||||||
const event = { pubkey, created_at, kind: 9734, tags: [...tags, ['anon', '']], content: '' };
|
const event = { pubkey, created_at, kind: 9734, tags: [...tags, ['anon', '']], content: ZAP_MEMO };
|
||||||
const id = await sha256Hex(JSON.stringify([0, pubkey, created_at, 9734, event.tags, '']));
|
const id = await sha256Hex(JSON.stringify([0, pubkey, created_at, 9734, event.tags, '']));
|
||||||
event.id = id;
|
event.id = id;
|
||||||
event.sig = hex(schnorr.sign(unhex(id), priv));
|
event.sig = hex(schnorr.sign(unhex(id), priv));
|
||||||
@@ -136,24 +142,36 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------- payment ----------
|
// ---------- payment ----------
|
||||||
const weblnReady = () => !!(window.webln && typeof window.webln.sendPayment === 'function');
|
// Provider sources: ① window.webln (extension) ② Bitcoin Connect's
|
||||||
|
// connected provider (NWC / LNC / LNbits) — handed to us via onConnected(),
|
||||||
|
// it is NOT set on window.webln.
|
||||||
|
let bcProvider = null;
|
||||||
|
|
||||||
// enable() once per session, started as early as possible (prompt shows
|
const getProvider = () => {
|
||||||
// immediately on click instead of after the lnurlp/invoice round trips)
|
if (window.webln && typeof window.webln.sendPayment === 'function') return window.webln;
|
||||||
|
if (bcProvider && typeof bcProvider.sendPayment === 'function') return bcProvider;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const weblnReady = () => !!getProvider();
|
||||||
|
|
||||||
|
// enable() once per provider+session, started as early as possible (prompt
|
||||||
|
// shows immediately on click instead of after the lnurlp/invoice round trips)
|
||||||
let enablePromise = null;
|
let enablePromise = null;
|
||||||
const ensureEnabled = () => {
|
let enabledFor = null;
|
||||||
|
const ensureEnabled = (provider) => {
|
||||||
|
if (enabledFor !== provider) { enablePromise = null; enabledFor = provider; }
|
||||||
if (!enablePromise) {
|
if (!enablePromise) {
|
||||||
enablePromise = (async () => {
|
enablePromise = (async () => {
|
||||||
if (window.webln?.enable) { try { await window.webln.enable(); } catch (e) { /* already enabled */ } }
|
if (provider?.enable) { try { await provider.enable(); } catch (e) { /* already enabled */ } }
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
return enablePromise;
|
return enablePromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function payInvoice(pr) {
|
async function payInvoice(pr, provider) {
|
||||||
if (!weblnReady()) throw new Error('wallet not connected');
|
if (!provider?.sendPayment) throw new Error('wallet not connected');
|
||||||
await ensureEnabled();
|
await ensureEnabled(provider);
|
||||||
return window.webln.sendPayment(pr);
|
return provider.sendPayment(pr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- lnurlp params (cached + prefetched on hover) ----------
|
// ---------- lnurlp params (cached + prefetched on hover) ----------
|
||||||
@@ -172,11 +190,13 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
toast(`⚡ Zapping <strong>${escapeHtml(name)}</strong>…`, 'info', 9000);
|
toast(`⚡ Zapping <strong>${escapeHtml(name)}</strong>…`, 'info', 9000);
|
||||||
|
const provider = getProvider();
|
||||||
|
if (!provider) throw new Error('wallet not connected');
|
||||||
// kick off wallet permission + lnurlp in parallel — the wallet's
|
// kick off wallet permission + lnurlp in parallel — the wallet's
|
||||||
// approval prompt (if any) shows right away, not after the fetches
|
// approval prompt (if any) shows right away, not after the fetches
|
||||||
const [params] = await Promise.all([
|
const [params] = await Promise.all([
|
||||||
fetchLnurlParamsCached(lnAddress),
|
fetchLnurlParamsCached(lnAddress),
|
||||||
weblnReady() ? ensureEnabled() : Promise.resolve()
|
ensureEnabled(provider)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let msat = ZAP_SATS * 1000;
|
let msat = ZAP_SATS * 1000;
|
||||||
@@ -186,7 +206,7 @@
|
|||||||
|
|
||||||
const { event, anon } = await buildZapRequest(params, msat, lnAddress);
|
const { event, anon } = await buildZapRequest(params, msat, lnAddress);
|
||||||
const inv = await fetchInvoice(params, msat, event);
|
const inv = await fetchInvoice(params, msat, event);
|
||||||
await payInvoice(inv.pr);
|
await payInvoice(inv.pr, provider);
|
||||||
|
|
||||||
if (chip) { chip.dataset.state = 'zapped'; delete chip.dataset.zapping; }
|
if (chip) { chip.dataset.state = 'zapped'; delete chip.dataset.zapping; }
|
||||||
const sats = msat / 1000;
|
const sats = msat / 1000;
|
||||||
@@ -224,6 +244,12 @@
|
|||||||
|
|
||||||
// ---------- connect-modal path ----------
|
// ---------- connect-modal path ----------
|
||||||
function openConnectModal() {
|
function openConnectModal() {
|
||||||
|
// preferred: the library's own API (module sets window.bitcoinConnect)
|
||||||
|
if (typeof window.bitcoinConnect?.launchModal === 'function') {
|
||||||
|
window.bitcoinConnect.launchModal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// fallback: click the real button inside the shadow root
|
||||||
const btn = document.querySelector('bc-button');
|
const btn = document.querySelector('bc-button');
|
||||||
if (!btn) return false;
|
if (!btn) return false;
|
||||||
const inner = btn.shadowRoot?.querySelector('bci-button');
|
const inner = btn.shadowRoot?.querySelector('bci-button');
|
||||||
@@ -231,6 +257,8 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// polls for extension-provided window.webln (Bitcoin Connect's NWC path
|
||||||
|
// is handled by the onConnected subscription instead)
|
||||||
function watchForConnection() {
|
function watchForConnection() {
|
||||||
clearInterval(pollTimer);
|
clearInterval(pollTimer);
|
||||||
pollTimer = setInterval(() => {
|
pollTimer = setInterval(() => {
|
||||||
@@ -357,6 +385,49 @@
|
|||||||
setTimeout(markReady, 1500);
|
setTimeout(markReady, 1500);
|
||||||
window.addEventListener('bc:onpaid', markReady);
|
window.addEventListener('bc:onpaid', markReady);
|
||||||
|
|
||||||
|
// ---------- Bitcoin Connect wiring (NWC / LNC / LNbits) ----------
|
||||||
|
// BC never sets window.webln — the connected provider arrives via
|
||||||
|
// onConnected(provider). The module (loaded as type=module, after this
|
||||||
|
// classic script) exposes window.bitcoinConnect, so retry until it lands.
|
||||||
|
let bcSubscribed = false;
|
||||||
|
function trySubscribeBc() {
|
||||||
|
if (bcSubscribed) return;
|
||||||
|
const api = window.bitcoinConnect;
|
||||||
|
if (!api || typeof api.onConnected !== 'function') return;
|
||||||
|
bcSubscribed = true;
|
||||||
|
try {
|
||||||
|
// fires immediately if a wallet is already connected (page reload),
|
||||||
|
// and again on every new connection
|
||||||
|
api.onConnected(provider => {
|
||||||
|
if (provider && typeof provider.sendPayment === 'function') {
|
||||||
|
bcProvider = provider;
|
||||||
|
enabledFor = null; // re-enable for the new provider
|
||||||
|
document.body.classList.add('webln-ready');
|
||||||
|
toast('Wallet connected ⚡', 'success', 2500);
|
||||||
|
if (pendingZap) {
|
||||||
|
const p = pendingZap; pendingZap = null;
|
||||||
|
setTimeout(() => zap(p.lnAddress, p.name, p.chip), 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (typeof api.onDisconnected === 'function') {
|
||||||
|
api.onDisconnected(() => {
|
||||||
|
bcProvider = null;
|
||||||
|
enabledFor = null;
|
||||||
|
document.body.classList.remove('webln-ready');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Bitcoin Connect subscription failed', e);
|
||||||
|
bcSubscribed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let bcTries = 0;
|
||||||
|
const bcWatch = setInterval(() => {
|
||||||
|
trySubscribeBc();
|
||||||
|
if (bcSubscribed || ++bcTries > 80) clearInterval(bcWatch); // ~20s max
|
||||||
|
}, 250);
|
||||||
|
|
||||||
// ---------- warm the caches so first zap / first dialog are instant ----------
|
// ---------- warm the caches so first zap / first dialog are instant ----------
|
||||||
const warmup = () => {
|
const warmup = () => {
|
||||||
loadQr().catch(() => {}); // QR generator (tiny)
|
loadQr().catch(() => {}); // QR generator (tiny)
|
||||||
|
|||||||
Reference in New Issue
Block a user