diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/db.js | 34 | ||||
| -rw-r--r-- | lib/invoices.js | 1 | ||||
| -rw-r--r-- | lib/lightning.js | 14 | ||||
| -rw-r--r-- | lib/mail.js | 14 | ||||
| -rw-r--r-- | lib/nostr.js | 88 | ||||
| -rw-r--r-- | lib/payments.js | 7 | ||||
| -rw-r--r-- | lib/register.js | 1 |
7 files changed, 146 insertions, 13 deletions
@@ -1,7 +1,7 @@ import config from "$config"; -import { sleep } from "$lib/utils"; +import { wait, sleep } from "$lib/utils"; import { createClient, defineScript } from "redis"; -import { warn } from "$lib/logging"; +import { warn, l, err } from "$lib/logging"; let SCRIPT = ` local balanceKey = KEYS[1] @@ -33,11 +33,35 @@ let debit = defineScript({ transformArguments: (...args) => args.map(a => a.toString()) }); -export const db = createClient({ +export let db = createClient({ url: config.db, - scripts: { debit } + scripts: { debit }, + socket: { + reconnectStrategy: retries => Math.min(retries * 50, 5000) + } +}); + + +async function connectWithRetry() { + try { + await db.connect(); + } catch (err) { + console.error('Failed to connect to Redis, retrying...', err); + setTimeout(connectWithRetry, 5000); // Retry after 5 seconds + } +} + +connectWithRetry(); + +db.on("error", e => { + if (e.message.startsWith("getaddr")) return; + err("Redis error", e.message); }); -await db.connect(); + +db.on("end", () => { + warn("Redis connection ended"); +}); + export default db; export let g = async k => { diff --git a/lib/invoices.js b/lib/invoices.js index 8ac49a79..9f2c0dc9 100644 --- a/lib/invoices.js +++ b/lib/invoices.js @@ -65,6 +65,7 @@ export let generate = async ({ invoice, user, sender, memo }) => { currency, hash, id, + memo, rate, pending: 0, received: 0, diff --git a/lib/lightning.js b/lib/lightning.js index 77a429ed..d4664458 100644 --- a/lib/lightning.js +++ b/lib/lightning.js @@ -1,4 +1,5 @@ import { emit } from "$lib/sockets"; +import { handleZap } from "$lib/nostr"; import config from "$config"; import { notify } from "$lib/notifications"; import { callWebhook } from "$lib/webhooks"; @@ -8,14 +9,15 @@ import { g, s, t, db } from "$lib/db"; import { credit, types } from "$lib/payments"; export async function listenForLightning() { - let event = await ln.waitanyinvoice((await g("pay_index")) || 0); + let inv = await ln.waitanyinvoice((await g("pay_index")) || 0); let { bolt11, + description, pay_index, status, amount_received_msat, payment_preimage: preimage - } = event; + } = inv; await s("pay_index", pay_index); setTimeout(listenForLightning); @@ -31,6 +33,14 @@ export async function listenForLightning() { let invoice = await g(`invoice:${id}`); if (!invoice) return warn("received lightning with no invoice", bolt11); + if (invoice && invoice.memo) { + try { + if (JSON.parse(description).kind === 9734) await handleZap(inv); + } catch (e) { + warn("failed to handle zap", e.message); + } + } + let p = await g(`payment:${bolt11}`); if (typeof p === "string") p = await g(`payment:${p}`); diff --git a/lib/mail.js b/lib/mail.js index b5fda14d..47ef5eac 100644 --- a/lib/mail.js +++ b/lib/mail.js @@ -14,12 +14,15 @@ export let templates = { export let mail = async (user, subject, template, params) => { try { + l("sending mail", user.username, subject); if (!user.email) return; - let source = fs.readFileSync(template, "utf8"); - let html = handlebars.compile(source, params); + let source = fs.readFileSync(templates[template], "utf8"); + let html = handlebars.compile(source)(params); let client = new SESClient({ region: "us-east-2" }); + return; + await client.send( new SendEmailCommand({ Destination: { @@ -38,3 +41,10 @@ export let mail = async (user, subject, template, params) => { err(e.message); } }; + +/* +mail({ username: "bob", email: "[email protected]" }, "Test", "paymentSent", { + username: "bob", + link: "https://coinos.io/api/verify/12345" +}); +*/ diff --git a/lib/nostr.js b/lib/nostr.js index 604d207d..3a370aec 100644 --- a/lib/nostr.js +++ b/lib/nostr.js @@ -1,9 +1,10 @@ import config from "$config"; -import { Relay, RelayPool } from "nostr"; +import { Relay, RelayPool, calculateId, signId, getPublicKey } from "nostr"; import { broadcast, emit } from "$lib/sockets"; import store from "$lib/store"; import { g, s, db } from "$lib/db"; -import { nada, wait } from "$lib/utils"; +import { fail, nada, wait } from "$lib/utils"; +import { nip19 } from "nostr-tools"; export let coinos; export let pool; @@ -178,3 +179,86 @@ let getUser = async pubkey => { } ); }; + +function send(ev, url, opts) { + let timeout = (opts && opts.timeout != null && opts.timeout) || 1000; + + return new Promise((resolve, reject) => { + let relay = Relay(url); + + function timeout_reached() { + relay.close(); + reject(new Error("Request timeout")); + } + + let timer = setTimeout(timeout_reached, timeout); + + relay.on("open", () => { + clearTimeout(timer); + timer = setTimeout(timeout_reached, timeout); + relay.send(["EVENT", ev]); + }); + + relay.on("ok", (evid, ok, msg) => { + clearTimeout(timer); + relay.close(); + resolve({ evid, ok, msg }); + }); + }); +} + +export async function handleZap(invoice) { + let { nostrKey: privkey } = config; + let pubkey = getPublicKey(privkey); + let keypair = { privkey, pubkey }; + let zapreq = JSON.parse(invoice.description); + + if (!zapreq.tags || zapreq.tags.length === 0) { + fail(`No tags found`); + } + + let ptags = zapreq.tags.filter( + t => t && t.length && t.length >= 2 && t[0] === "p" + ); + + if (ptags.length !== 1) { + fail(`None or multiple p tags found`); + } + + let etags = zapreq.tags.filter( + t => t && t.length && t.length >= 2 && t[0] === "e" + ); + + if (!(etags.length === 0 || etags.length === 1)) { + fail(`Expected none or 1 e tags`); + } + + let relays_tag = zapreq.tags.find( + t => t && t.length && t.length >= 2 && t[0] === "relays" + ); + + if (!relays_tag) { + fail(`No relays tag found`); + } + + let relays = relays_tag.slice(1).filter(r => r && r.startsWith("ws")); + let etag = etags.length > 0 && etags[0]; + let ptag = ptags[0]; + + let kind = 9735; + let created_at = invoice.paid_at; + let content = zapreq.content; + + let tags = [ptag]; + if (etag) tags.push(etag); + + tags.push(["bolt11", invoice.bolt11]); + tags.push(["description", invoice.description]); + tags.push(["preimage", invoice.payment_preimage]); + + let ev = { pubkey, kind, created_at, content, tags }; + ev.id = await calculateId(ev); + ev.sig = await signId(privkey, ev.id); + + await Promise.all(relays.map(r => send(ev, r))); +} diff --git a/lib/payments.js b/lib/payments.js index 9f32f8fb..f2fab1ba 100644 --- a/lib/payments.js +++ b/lib/payments.js @@ -113,9 +113,12 @@ export let credit = async (hash, amount, memo, ref, type = types.internal) => { if (iid && iid.hash) iid = iid.hash; let invoice = await g(`invoice:${iid}`); - if (!invoice) return warn("failed to credit", hash, ", invoice not found"); + if (!invoice) { + await db.sAdd("missing", hash); + return; + } - let { tip } = invoice; + let { tip } = invoice; tip = parseInt(tip) || 0; if (!memo) ({ memo } = invoice); diff --git a/lib/register.js b/lib/register.js index 798565b9..c52dad03 100644 --- a/lib/register.js +++ b/lib/register.js @@ -45,6 +45,7 @@ export default async (user, ip, requireChallenge) => { user.fiat = false; user.otpsecret = authenticator.generateSecret(); user.migrated = true; + user.locktime = 300; await s(`user:${id}`, user); await s(`user:${username}`, id); |
