diff --git a/js/app.js b/js/app.js index 88a28b9f..9b634431 100644 --- a/js/app.js +++ b/js/app.js @@ -5910,6 +5910,69 @@ class Application { shares, shareTracker, }) { + const summarizeTracker = (tracker) => + Array.isArray(tracker) + ? tracker.map((entry) => ({ + type: entry?.type || "unknown", + status: entry?.status || "unknown", + amount: + Number.isFinite(entry?.amount) && entry.amount >= 0 + ? entry.amount + : undefined, + address: entry?.address || undefined, + hasPayment: Boolean(entry?.payment), + errorMessage: + typeof entry?.error?.message === "string" + ? entry.error.message + : entry?.error + ? String(entry.error) + : undefined, + })) + : undefined; + const logZapError = (stage, details, error) => { + const summary = { + stage, + shareType: details?.shareType, + address: details?.address, + amount: + Number.isFinite(details?.amount) && details.amount >= 0 + ? details.amount + : undefined, + overrideFee: + typeof details?.overrideFee === "number" + ? details.overrideFee + : undefined, + commentLength: + typeof details?.comment === "string" + ? details.comment.length + : undefined, + wallet: + details?.walletSettings + ? { + hasUri: Boolean(details.walletSettings.nwcUri), + type: + details.walletSettings.type || + details.walletSettings.name || + details.walletSettings.client || + undefined, + } + : undefined, + shares: details?.context?.shares + ? { + total: details.context.shares.total, + creator: details.context.shares.creatorShare, + platform: details.context.shares.platformShare, + } + : undefined, + tracker: summarizeTracker(details?.tracker), + retryAttempt: + Number.isInteger(details?.retryAttempt) && details.retryAttempt >= 0 + ? details.retryAttempt + : undefined, + }; + console.error("[zap] Modal zap failure", summary, error); + }; + const lightningAddress = this.currentVideo?.lightningAddress || ""; const creatorKey = normalizeLightningAddressKey( creatorEntry?.address || lightningAddress @@ -6020,6 +6083,17 @@ class Application { error, }); } + logZapError( + "wallet.sendPayment", + { + shareType: activeShare || "unknown", + amount, + address, + tracker: shareTracker, + context: { shares }, + }, + error + ); throw error; } finally { activeShare = null; @@ -6126,10 +6200,29 @@ class Application { return null; } - const context = await this.prepareModalLightningContext({ - amount, - overrideFee, - }); + let context; + try { + context = await this.prepareModalLightningContext({ + amount, + overrideFee, + }); + } catch (error) { + console.error( + "[zap] Modal lightning context failed", + { + amount, + overrideFee, + commentLength: typeof comment === "string" ? comment.length : undefined, + wallet: { + hasUri: Boolean(settings?.nwcUri), + type: + settings?.type || settings?.name || settings?.client || undefined, + }, + }, + error + ); + throw error; + } const shareTracker = []; const dependencies = this.createModalZapDependencies({ ...context, @@ -6159,6 +6252,39 @@ class Application { if (Array.isArray(shareTracker) && shareTracker.length) { error.__zapShareTracker = shareTracker; } + console.error( + "[zap] splitAndZap failed", + { + amount, + overrideFee, + commentLength: typeof comment === "string" ? comment.length : undefined, + wallet: { + hasUri: Boolean(settings?.nwcUri), + type: + settings?.type || settings?.name || settings?.client || undefined, + }, + shares: { + total: context?.shares?.total, + creator: context?.shares?.creatorShare, + platform: context?.shares?.platformShare, + }, + tracker: Array.isArray(shareTracker) + ? shareTracker.map((entry) => ({ + type: entry?.type || "unknown", + status: entry?.status || "unknown", + amount: entry?.amount, + address: entry?.address, + errorMessage: + typeof entry?.error?.message === "string" + ? entry.error.message + : entry?.error + ? String(entry.error) + : undefined, + })) + : undefined, + }, + error + ); throw error; } finally { if (hasGlobal && typeof overrideFee === "number" && Number.isFinite(overrideFee)) { @@ -6224,6 +6350,31 @@ class Application { const tracker = Array.isArray(error?.__zapShareTracker) ? error.__zapShareTracker : []; + console.error( + "[zap] Modal zap retry failed", + { + shareType: share?.type || "unknown", + amount: share?.amount, + address: share?.address, + commentLength: + typeof (retryState?.comment || comment) === "string" + ? (retryState?.comment || comment).length + : undefined, + tracker: tracker.map((entry) => ({ + type: entry?.type || "unknown", + status: entry?.status || "unknown", + amount: entry?.amount, + address: entry?.address, + errorMessage: + typeof entry?.error?.message === "string" + ? entry.error.message + : entry?.error + ? String(entry.error) + : undefined, + })), + }, + error + ); if (tracker.length) { aggregatedTracker.push(...tracker); } @@ -6358,6 +6509,37 @@ class Application { const tracker = Array.isArray(error?.__zapShareTracker) ? error.__zapShareTracker : []; + console.error( + "[zap] Modal zap attempt failed", + { + amount, + commentLength: typeof comment === "string" ? comment.length : undefined, + wallet: { + hasUri: Boolean(walletSettings?.nwcUri), + type: + walletSettings?.type || + walletSettings?.name || + walletSettings?.client || + undefined, + }, + tracker: tracker.map((entry) => ({ + type: entry?.type || "unknown", + status: entry?.status || "unknown", + amount: entry?.amount, + address: entry?.address, + errorMessage: + typeof entry?.error?.message === "string" + ? entry.error.message + : entry?.error + ? String(entry.error) + : undefined, + })), + retryPending: Array.isArray(this.modalZapRetryState?.shares) + ? this.modalZapRetryState.shares.length + : 0, + }, + error + ); if (tracker.length) { this.videoModal?.renderZapReceipts(tracker, { partial: true }); } diff --git a/js/channelProfile.js b/js/channelProfile.js index 2b274926..ca60f0a4 100644 --- a/js/channelProfile.js +++ b/js/channelProfile.js @@ -38,6 +38,67 @@ const getApp = () => getApplication(); let currentChannelHex = null; let currentChannelNpub = null; let currentChannelLightningAddress = ""; + +const summarizeZapTracker = (tracker) => + Array.isArray(tracker) + ? tracker.map((entry) => ({ + type: entry?.type || "unknown", + status: entry?.status || "unknown", + amount: + Number.isFinite(entry?.amount) && entry.amount >= 0 + ? entry.amount + : undefined, + address: entry?.address || undefined, + hasPayment: Boolean(entry?.payment), + errorMessage: + typeof entry?.error?.message === "string" + ? entry.error.message + : entry?.error + ? String(entry.error) + : undefined, + })) + : undefined; + +function logZapError(stage, details = {}, error) { + const summary = { + stage, + shareType: details?.shareType, + address: details?.address, + amount: + Number.isFinite(details?.amount) && details.amount >= 0 + ? details.amount + : undefined, + overrideFee: + typeof details?.overrideFee === "number" + ? details.overrideFee + : undefined, + commentLength: + typeof details?.comment === "string" ? details.comment.length : undefined, + wallet: details?.walletSettings + ? { + hasUri: Boolean(details.walletSettings.nwcUri), + type: + details.walletSettings.type || + details.walletSettings.name || + details.walletSettings.client || + undefined, + } + : undefined, + shares: details?.context?.shares + ? { + total: details.context.shares.total, + creator: details.context.shares.creatorShare, + platform: details.context.shares.platformShare, + } + : undefined, + tracker: summarizeZapTracker(details?.tracker), + retryAttempt: + Number.isInteger(details?.retryAttempt) && details.retryAttempt >= 0 + ? details.retryAttempt + : undefined, + }; + console.error("[zap] Channel zap failure", summary, error); +} let currentChannelProfileEvent = null; let cachedZapButton = null; @@ -519,6 +580,17 @@ function createZapDependencies({ creatorEntry, platformEntry, shares, shareTrack error, }); } + logZapError( + "wallet.sendPayment", + { + shareType: activeShare || "unknown", + amount, + address, + tracker: shareTracker, + context: { shares }, + }, + error + ); throw error; } finally { activeShare = null; @@ -569,7 +641,21 @@ async function runZapAttempt({ amount, overrideFee = null, walletSettings }) { return null; } - const context = await prepareLightningContext({ amount, overrideFee }); + let context; + try { + context = await prepareLightningContext({ amount, overrideFee }); + } catch (error) { + logZapError( + "prepareLightningContext", + { + amount, + overrideFee, + walletSettings: settings, + }, + error + ); + throw error; + } const shareTracker = []; const dependencies = createZapDependencies({ ...context, @@ -598,6 +684,17 @@ async function runZapAttempt({ amount, overrideFee = null, walletSettings }) { if (Array.isArray(shareTracker) && shareTracker.length) { error.__zapShareTracker = shareTracker; } + logZapError( + "splitAndZap", + { + amount, + overrideFee, + walletSettings: settings, + context, + tracker: shareTracker, + }, + error + ); throw error; } finally { if (hasGlobal && typeof overrideFee === "number" && Number.isFinite(overrideFee)) { @@ -654,6 +751,17 @@ async function executePendingRetry({ walletSettings }) { const tracker = Array.isArray(error?.__zapShareTracker) ? error.__zapShareTracker : []; + logZapError( + "retry.share", + { + shareType: share?.type || "unknown", + amount: share?.amount, + address: share?.address, + walletSettings, + tracker, + }, + error + ); if (tracker.length) { aggregatedTracker.push(...tracker); } @@ -714,6 +822,7 @@ async function handleZapButtonClick(event) { zapButton.classList.add("opacity-50", "pointer-events-none"); amountInput.disabled = true; + let attemptedAmount = null; try { if (pendingZapRetry?.shares?.length) { await executePendingRetry({ walletSettings }); @@ -721,6 +830,7 @@ async function handleZapButtonClick(event) { } const amount = Math.max(0, Math.round(Number(amountInput.value || 0))); + attemptedAmount = amount; if (!amount) { const message = "Enter a zap amount greater than zero."; setZapStatus(message, "error"); @@ -753,6 +863,19 @@ async function handleZapButtonClick(event) { const tracker = Array.isArray(error?.__zapShareTracker) ? error.__zapShareTracker : []; + logZapError( + "handleZapButtonClick", + { + amount: attemptedAmount, + walletSettings, + tracker, + retryAttempt: + Array.isArray(pendingZapRetry?.shares) && pendingZapRetry.shares.length + ? pendingZapRetry.shares.length + : undefined, + }, + error + ); if (tracker.length) { renderZapReceipts(tracker, { partial: true }); }