aboutsummaryrefslogtreecommitdiffstats
path: root/lib/notifications.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/notifications.ts')
-rw-r--r--lib/notifications.ts74
1 files changed, 57 insertions, 17 deletions
diff --git a/lib/notifications.ts b/lib/notifications.ts
index d5b4f73f..fe245c61 100644
--- a/lib/notifications.ts
+++ b/lib/notifications.ts
@@ -4,11 +4,11 @@ import ln from "$lib/ln";
import { err, l, warn } from "$lib/logging";
import { mail, templates } from "$lib/mail";
import mqtt from "$lib/mqtt";
-import { publish, serverSecret } from "$lib/nostr";
+import { encryptPayload, publish, serverSecret2 } from "$lib/nostr";
import { emit } from "$lib/sockets";
import { f, fiat, fmt, getUser, link, nada, t } from "$lib/utils";
import { hexToBytes } from "@noble/hashes/utils";
-import { finalizeEvent, nip04 } from "nostr-tools";
+import { finalizeEvent } from "nostr-tools";
import webpush from "web-push";
if (config.vapid) {
@@ -19,6 +19,22 @@ if (config.vapid) {
);
}
+
+// LNURL-pay invoices carry the raw metadata array as their memo when the
+// payer left no comment, e.g. [["text/plain","Paying [email protected]"],...].
+// Receipts should show just the human-readable text.
+const printerMemo = (memo) => {
+ if (typeof memo !== "string" || !memo.startsWith("[")) return memo;
+ try {
+ const meta = JSON.parse(memo);
+ if (Array.isArray(meta)) {
+ const plain = meta.find((m) => Array.isArray(m) && m[0] === "text/plain");
+ if (plain) return plain[1];
+ }
+ } catch (e) {}
+ return memo;
+};
+
export const notify = async (p, user, withdrawal) => {
emit(user.id, "payment", p);
let { username } = user;
@@ -74,7 +90,8 @@ export const notify = async (p, user, withdrawal) => {
if (!mqtt.connected) await mqtt.reconnect();
mqtt.publish(
username,
- `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${p.items}`,
+ `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${printerMemo(p.memo)}:${p.items}`,
+ { qos: 1 },
);
}
};
@@ -84,11 +101,23 @@ export const nwcNotify = async (p) => {
const user = await getUser(p.uid);
const pubkeys = await db.sMembers(`${user.id}:apps`);
if (pubkeys.length) {
- let payment_hash = "";
- if (p.type === "lightning") ({ payment_hash } = await ln.decode(p.hash));
+ let payment_hash = p.payment_hash || "";
+ if (!payment_hash && (p.type === "lightning" || p.type === "bolt12")) {
+ try {
+ const d = await ln.decode(p.hash);
+ // bolt11 decodes expose payment_hash, bolt12 invoices
+ // invoice_payment_hash
+ payment_hash = d.payment_hash || d.invoice_payment_hash || "";
+ } catch (e) {}
+ }
for (const pubkey of pubkeys) {
- const { notify } = await g(`app:${pubkey}`);
- if (!notify) continue;
+ // The app record may be missing/not-yet-written: a pubkey lands in
+ // `${uid}:apps` before its `app:<pubkey>` is persisted (creation race when
+ // adding an NWC connection), or a stale entry outlived its record. Guard
+ // the lookup — destructuring `notify` off null threw and, since the catch
+ // wraps the whole loop, aborted notifications for ALL the user's apps.
+ const app = await g(`app:${pubkey}`);
+ if (!app?.notify) continue;
l("notifying", pubkey, p.type, p.amount);
const notification = {
@@ -108,22 +137,33 @@ export const nwcNotify = async (p) => {
notification,
});
- const content = await nip04.encrypt(serverSecret, pubkey, payload);
+ // NIP-47: a wallet supporting both schemes publishes each notification
+ // twice — kind 23196 nip04-encrypted, kind 23197 nip44-encrypted
+ for (const [scheme, kind] of [
+ ["nip04", 23196],
+ ["nip44_v2", 23197],
+ ] as const) {
+ const content = await encryptPayload(
+ scheme,
+ serverSecret2,
+ pubkey,
+ payload,
+ );
- const unsigned = {
- content,
- tags: [["p", pubkey]],
- kind: 23196,
- created_at: Math.floor(Date.now() / 1000),
- };
+ const unsigned = {
+ content,
+ tags: [["p", pubkey]],
+ kind,
+ created_at: Math.floor(Date.now() / 1000),
+ };
- const event = finalizeEvent(unsigned, hexToBytes(serverSecret));
+ const event = finalizeEvent(unsigned, hexToBytes(serverSecret2));
- publish(event).catch(nada);
+ publish(event).catch(nada);
+ }
}
}
} catch (e) {
- console.log(e);
warn("nwc notification failed", e.message);
}
};