aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/features/nwc.js17
-rw-r--r--src/i18n.js2
-rw-r--r--src/nwc-respond.js11
-rw-r--r--src/sw-nwc.js2
4 files changed, 30 insertions, 2 deletions
diff --git a/src/features/nwc.js b/src/features/nwc.js
index 686b11f..d6bfdea 100644
--- a/src/features/nwc.js
+++ b/src/features/nwc.js
@@ -212,6 +212,22 @@ export function nwcFeature(ctx) {
? nip44.encrypt(txt, nip44.getConversationKey(sk, pk))
: nip04.encrypt(sk, pk, txt));
+ // A pay answered by a hidden tab is as invisible as one paid by the
+ // worker — surface it the same way. A visible tab already shows it live.
+ async function notifySent(c, amountSat, feeSat) {
+ try {
+ if (typeof document === 'undefined' || !document.hidden) return;
+ if (typeof Notification === 'undefined' || Notification.permission !== 'granted') return;
+ const reg = await navigator.serviceWorker.ready;
+ await reg.showNotification(t('nwcPaidTitle'), {
+ body: t('nwcPaidBody', { amount: amountSat.toLocaleString(), name: c.name || 'app' })
+ + (feeSat ? ` (+${feeSat} fee)` : ''),
+ icon: 'icon-192.png', badge: 'icon-192.png',
+ tag: 'nwc-sent', renotify: true, data: { url: './' },
+ });
+ } catch {}
+ }
+
// Every request gets a reply, including failures — a client that never
// hears back just hangs.
async function reply(c, ev, scheme, payload) {
@@ -358,6 +374,7 @@ export function nwcFeature(ctx) {
const res = await hook('arkPayInvoice', invoice, { maxAmountSat: c.maxSat });
// the ark seam throws on failure, so reaching here means it settled
recordSpend(c, (res.amountSat || 0) + (res.feeSat || 0));
+ notifySent(c, res.amountSat || 0, res.feeSat || 0);
return { result: { preimage: res.preimage, fees_paid: (res.feeSat || 0) * 1000 } };
}
diff --git a/src/i18n.js b/src/i18n.js
index 85f8308..a211eb5 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -663,6 +663,8 @@ const STR = {
nwcOff: 'Off',
nwcBackgroundOn: 'Requests will be answered in the background',
nwcBackgroundOff: 'Background answering turned off',
+ nwcPaidTitle: 'Payment sent',
+ nwcPaidBody: '-{amount} sats via {name}',
nwcNotifHint: 'You’ll be asked to allow notifications — the connection uses them to answer when Coinos is closed.',
nwcNotifNeeded: 'Without notifications the connection couldn’t answer when Coinos is closed, so it wasn’t added. Allow notifications and try again.',
nwcNotifBlocked: 'Notifications are blocked for this site. Allow them in your browser settings, then add the connection again.',
diff --git a/src/nwc-respond.js b/src/nwc-respond.js
index 258b7e7..bd75360 100644
--- a/src/nwc-respond.js
+++ b/src/nwc-respond.js
@@ -280,5 +280,14 @@ export async function respondFromBg(data, {
result: { preimage: a.preimage, fees_paid: (a.feeSat || 0) * 1000 },
});
log(`paid ${a.amountSat || dec.amountSat} sat via the ASP from the background mirror`);
- return true;
+ // Money left while the app was closed — that always deserves a heads-up,
+ // whether it was a zap from a nostr app or any other connected spender.
+ const paidSat = a.amountSat || dec.amountSat;
+ const feeNote = a.feeSat ? ` (+${a.feeSat} fee)` : '';
+ return {
+ notify: {
+ title: 'Payment sent', tag: 'nwc-sent',
+ body: `-${paidSat.toLocaleString()} sats${feeNote} via ${conn.name || 'a connected app'}`,
+ },
+ };
}
diff --git a/src/sw-nwc.js b/src/sw-nwc.js
index a73a611..108c393 100644
--- a/src/sw-nwc.js
+++ b/src/sw-nwc.js
@@ -80,7 +80,7 @@ self.addEventListener('push', (e) => {
await self.registration.showNotification(handled.notify.title, {
body: handled.notify.body,
icon: 'icon-192.png', badge: 'icon-192.png',
- tag: 'nwc-incoming', renotify: true,
+ tag: handled.notify.tag || 'nwc-incoming', renotify: true,
data: { url: './' },
});
return;