diff --git a/packages/app/src/Element/NoteFooter.tsx b/packages/app/src/Element/NoteFooter.tsx
index d8e6944..0fa03cb 100644
--- a/packages/app/src/Element/NoteFooter.tsx
+++ b/packages/app/src/Element/NoteFooter.tsx
@@ -118,7 +118,7 @@ export default function NoteFooter(props: NoteFooterProps) {
}
}
- async function zapClick(e: React.MouseEvent) {
+ async function fastZap(e: React.MouseEvent) {
if (zapping || e.isPropagationStopped()) return;
const lnurl = author?.lud16 || author?.lud06;
@@ -149,7 +149,7 @@ export default function NoteFooter(props: NoteFooterProps) {
if (service) {
return (
<>
-
zapClick(e)}>
+
fastZap(e)}>
{zapping ? : webln?.enabled ? : }
{zapTotal > 0 &&
{formatShort(zapTotal)}
}
diff --git a/packages/app/src/Element/SendSats.tsx b/packages/app/src/Element/SendSats.tsx
index 593289d..cdb9cd2 100644
--- a/packages/app/src/Element/SendSats.tsx
+++ b/packages/app/src/Element/SendSats.tsx
@@ -17,7 +17,7 @@ import Copy from "Element/Copy";
import useWebln from "Hooks/useWebln";
import messages from "./messages";
-import { LNURL, LNURLInvoice, LNURLSuccessAction } from "LNURL";
+import { LNURL, LNURLError, LNURLErrorCode, LNURLInvoice, LNURLSuccessAction } from "LNURL";
enum ZapType {
PublicZap = 1,
@@ -96,7 +96,7 @@ export default function SendSats(props: SendSatsProps) {
try {
const h = new LNURL(props.lnurl);
setHandler(h);
- h.load().catch(e => setError((e as Error).message));
+ h.load().catch(e => handleLNURLError(e, formatMessage(messages.InvoiceFail)));
} catch (e) {
if (e instanceof Error) {
setError(e.message);
@@ -147,10 +147,26 @@ export default function SendSats(props: SendSatsProps) {
await payWebLNIfEnabled(rsp);
}
} catch (e) {
- setError(formatMessage(messages.InvoiceFail));
+ handleLNURLError(e, formatMessage(messages.InvoiceFail));
}
}
+ function handleLNURLError(e: unknown, fallback: string) {
+ if (e instanceof LNURLError) {
+ switch (e.code) {
+ case LNURLErrorCode.ServiceUnavailable: {
+ setError(formatMessage(messages.LNURLFail));
+ return;
+ }
+ case LNURLErrorCode.InvalidLNURL: {
+ setError(formatMessage(messages.InvalidLNURL));
+ return;
+ }
+ }
+ }
+ setError(fallback);
+ }
+
function custom() {
if (!handler) return null;
const min = handler.min / 1000;
diff --git a/packages/app/src/Element/messages.ts b/packages/app/src/Element/messages.ts
index 0eb7717..eeee068 100644
--- a/packages/app/src/Element/messages.ts
+++ b/packages/app/src/Element/messages.ts
@@ -60,6 +60,7 @@ export default defineMessages({
Milliseconds: { defaultMessage: "{n} ms" },
ShowLatest: { defaultMessage: "Show latest {n} notes" },
LNURLFail: { defaultMessage: "Failed to load LNURL service" },
+ InvalidLNURL: { defaultMessage: "Invalid LNURL" },
InvoiceFail: { defaultMessage: "Failed to load invoice" },
Custom: { defaultMessage: "Custom" },
Confirm: { defaultMessage: "Confirm" },
diff --git a/packages/app/src/LNURL.ts b/packages/app/src/LNURL.ts
index c7c4cf8..8184af7 100644
--- a/packages/app/src/LNURL.ts
+++ b/packages/app/src/LNURL.ts
@@ -4,25 +4,47 @@ import { bech32ToText, unwrap } from "Util";
const PayServiceTag = "payRequest";
+export enum LNURLErrorCode {
+ ServiceUnavailable = 1,
+ InvalidLNURL = 2,
+}
+
+export class LNURLError extends Error {
+ code: LNURLErrorCode;
+
+ constructor(code: LNURLErrorCode, msg: string) {
+ super(msg);
+ this.code = code;
+ }
+}
+
export class LNURL {
#url: URL;
#service?: LNURLService;
+ /**
+ * Setup LNURL service
+ * @param lnurl bech32 lnurl / lightning address / https url
+ */
constructor(lnurl: string) {
lnurl = lnurl.toLowerCase().trim();
if (lnurl.startsWith("lnurl")) {
const decoded = bech32ToText(lnurl);
if (!decoded.startsWith("http")) {
- throw new Error("Invalid LNURL: not a url");
+ throw new LNURLError(LNURLErrorCode.InvalidLNURL, "Not a url");
}
this.#url = new URL(decoded);
} else if (lnurl.match(EmailRegex)) {
const [handle, domain] = lnurl.split("@");
this.#url = new URL(`https://${domain}/.well-known/lnurlp/${handle}`);
- } else if (lnurl.startsWith("http")) {
+ } else if (lnurl.startsWith("https:")) {
this.#url = new URL(lnurl);
+ } else if (lnurl.startsWith("lnurlp:")) {
+ const tmp = new URL(lnurl);
+ tmp.protocol = "https:";
+ this.#url = tmp;
} else {
- throw new Error("Invalid LNURL: could not determine service url");
+ throw new LNURLError(LNURLErrorCode.InvalidLNURL, "Could not determine service url");
}
}
@@ -75,10 +97,10 @@ export class LNURL {
return data;
}
} else {
- throw new Error(`Failed to fetch invoice (${rsp.statusText})`);
+ throw new LNURLError(LNURLErrorCode.ServiceUnavailable, `Failed to fetch invoice (${rsp.statusText})`);
}
} catch (e) {
- throw new Error("Failed to load callback");
+ throw new LNURLError(LNURLErrorCode.ServiceUnavailable, "Failed to load callback");
}
}
@@ -112,10 +134,10 @@ export class LNURL {
#validateService() {
if (this.#service?.tag !== PayServiceTag) {
- throw new Error("Invalid service: only lnurlp is supported");
+ throw new LNURLError(LNURLErrorCode.InvalidLNURL, "Only LNURLp is supported");
}
if (!this.#service?.callback) {
- throw new Error("Invalid service: no callback url");
+ throw new LNURLError(LNURLErrorCode.InvalidLNURL, "No callback url");
}
}
}
diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json
index a4b877c..4b3d625 100644
--- a/packages/app/src/lang.json
+++ b/packages/app/src/lang.json
@@ -30,6 +30,9 @@
"0BUTMv": {
"defaultMessage": "Search..."
},
+ "0jOEtS": {
+ "defaultMessage": "Invalid LNURL"
+ },
"0mch2Y": {
"defaultMessage": "name has disallowed characters"
},
diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json
index 9c8ef3d..4c149f1 100644
--- a/packages/app/src/translations/en.json
+++ b/packages/app/src/translations/en.json
@@ -9,6 +9,7 @@
"/d6vEc": "Make your profile easier to find and share",
"/n5KSF": "{n} ms",
"0BUTMv": "Search...",
+ "0jOEtS": "Invalid LNURL",
"0mch2Y": "name has disallowed characters",
"0yO7wF": "{n} secs",
"1A7TZk": "What is Snort and how does it work?",
@@ -268,4 +269,4 @@
"zjJZBd": "You're ready!",
"zonsdq": "Failed to load LNURL service",
"zvCDao": "Automatically show latest notes"
-}
+}
\ No newline at end of file