mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-10 14:52:57 +00:00
zap: fix NWC connection, dialog UX, and receipt reporting/discovery
- NWC connection was fully broken: parseNwcUri passed a hex secret to nostr-tools getPublicKey (needs Uint8Array) and threw. Convert hex->bytes. - A successful zap was shown as a red error when the kind-9735 receipt couldn't be validated. The NWC preimage already proves payment, so a paid-but-unconfirmed share is now a success-with-note, not a failure. - Receipt discovery: query by the reliably-indexed #e/#a/#p tags instead of #bolt11 (which most relays don't index), and poll a few times since the receipt is published shortly after payment. - In-flight "Sending..." status no longer mis-toned as a warning; success path fully resets the form; an in-flight-open click no longer cancels the open (which made the popover need many clicks); fee fallback verified no-bypass. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
1921356558
commit
c5589a1514
9 files changed
+448
-101
No files matched your search
+44
-40
@@ -213,7 +213,9 @@ export default class ZapController {
|
||||
|
||||
this.modalZapInFlight = true;
|
||||
this.videoModal?.setZapPending(true);
|
||||
this.videoModal?.setZapStatus(`Sending ${roundedAmount} sats…`, "warning");
|
||||
// In-flight/pending status is informational, not a warning — "warning" colored
|
||||
// it alarmingly (text-warning-strong) and counted as a warning in the flow.
|
||||
this.videoModal?.setZapStatus(`Sending ${roundedAmount} sats…`, "neutral");
|
||||
this.videoModal?.clearZapReceipts();
|
||||
|
||||
try {
|
||||
@@ -351,54 +353,37 @@ export default class ZapController {
|
||||
return validationStatus !== "passed";
|
||||
});
|
||||
|
||||
if (unvalidatedReceipts.length) {
|
||||
this.videoModal?.renderZapReceipts(receipts, { partial: true });
|
||||
|
||||
const warningSummary = unvalidatedReceipts
|
||||
.map((receipt) => {
|
||||
const type = receipt.recipientType || receipt.type || "creator";
|
||||
const label =
|
||||
type === "platform" ? "Platform" : type === "creator" ? "Creator" : "Lightning";
|
||||
const address =
|
||||
typeof receipt.address === "string" && receipt.address
|
||||
? ` (${receipt.address})`
|
||||
: "";
|
||||
const reason =
|
||||
typeof receipt.validation?.reason === "string" && receipt.validation.reason
|
||||
? ` — ${receipt.validation.reason}`
|
||||
: " — Awaiting compliant receipt.";
|
||||
return `${label}${address}${reason}`;
|
||||
})
|
||||
.join(" ");
|
||||
|
||||
const warningMessage = warningSummary
|
||||
? `Awaiting validated zap receipt. ${warningSummary}`
|
||||
: "Awaiting validated zap receipt from the recipient.";
|
||||
|
||||
this.videoModal?.setZapStatus(warningMessage, "warning");
|
||||
this.notifyError(warningMessage);
|
||||
|
||||
this.resetRetryState();
|
||||
this.modalZapCommentValue = "";
|
||||
this.videoModal?.resetZapForm({ amount: "", comment: "" });
|
||||
this.videoModal?.setZapCompleted(true);
|
||||
this.applyDefaultAmount();
|
||||
return;
|
||||
}
|
||||
|
||||
this.videoModal?.renderZapReceipts(receipts, { partial: false });
|
||||
// NOTE: `unvalidatedReceipts` are shares whose PAYMENT SUCCEEDED but whose
|
||||
// on-relay zap receipt (kind 9735) couldn't be validated. This is NOT a
|
||||
// failure: the NWC payment already returned a preimage (proof the invoice
|
||||
// was paid), and the 9735 is supplementary — the recipient's service may
|
||||
// publish it to relays we don't watch, or late, or not at all. Treating it
|
||||
// as an error wrongly told users a successful zap had failed. So we report
|
||||
// SUCCESS and only append a soft, non-alarming note that the receipt
|
||||
// couldn't be confirmed on relays.
|
||||
this.videoModal?.renderZapReceipts(receipts, {
|
||||
partial: unvalidatedReceipts.length > 0,
|
||||
});
|
||||
|
||||
const creatorShare = context.shares.creatorShare;
|
||||
const platformShare = context.shares.platformShare;
|
||||
const summary = platformShare
|
||||
let summary = platformShare
|
||||
? `Sent ${context.shares.total} sats (creator ${creatorShare}, platform ${platformShare}).`
|
||||
: `Sent ${context.shares.total} sats to the creator.`;
|
||||
if (unvalidatedReceipts.length) {
|
||||
summary +=
|
||||
" Payment went through; couldn't confirm the zap receipt on relays.";
|
||||
}
|
||||
this.videoModal?.setZapStatus(summary, "success");
|
||||
this.notifySuccess("Zap sent successfully!");
|
||||
|
||||
this.resetRetryState();
|
||||
this.modalZapCommentValue = "";
|
||||
this.videoModal?.setZapComment("");
|
||||
// Reset the whole form (amount + comment) after a successful send, matching
|
||||
// the validation-warning terminal path above. Previously this only cleared
|
||||
// the comment via setZapComment, leaving the form in a half-reset state
|
||||
// inconsistent with the other post-send paths and the modal's reset API.
|
||||
this.videoModal?.resetZapForm({ amount: "", comment: "" });
|
||||
this.videoModal?.setZapCompleted(true);
|
||||
this.applyDefaultAmount();
|
||||
} catch (error) {
|
||||
@@ -486,9 +471,28 @@ export default class ZapController {
|
||||
const lightningAddress = this.getCurrentVideo()?.lightningAddress || "";
|
||||
if (lightningAddress) {
|
||||
try {
|
||||
await fetchLightningMetadata(lightningAddress);
|
||||
const entry = await fetchLightningMetadata(lightningAddress);
|
||||
// Reachable from the browser. If the host doesn't advertise Nostr zaps,
|
||||
// a payment may still go through but won't yield a zap receipt — note it.
|
||||
if (entry?.metadata && entry.metadata.allowsNostr === false) {
|
||||
this.videoModal?.setZapStatus(
|
||||
"This creator's Lightning host doesn't advertise Nostr zaps — a payment may not produce a zap receipt.",
|
||||
"warning",
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
devLogger.warn("[zap] Failed to preload creator metadata:", error);
|
||||
// Passive heads-up to the zapper: if WE can't reach the creator's LNURL
|
||||
// host from the browser, the actual zap will fail the same way. Surface it
|
||||
// up front so they aren't surprised mid-send. (sendZap overwrites this with
|
||||
// its own status once they try.)
|
||||
if (error?.code === "lnurl-unreachable") {
|
||||
this.videoModal?.setZapStatus(
|
||||
"This creator's Lightning address can't be reached from a browser (its host may be " +
|
||||
"offline or blocking browser requests), so zapping them on bitvid may fail.",
|
||||
"warning",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user