From 9b2fb3b6bf0d6bf38340c1fc7472856e3d540658 Mon Sep 17 00:00:00 2001 From: Sam Samskies Date: Wed, 19 Apr 2023 13:57:58 -0500 Subject: [PATCH 1/4] don't render nostr:npub as a link People often type the literal text `nostr:npub` when talking about nostr URIs. That shouldn't be converted to a link. --- packages/app/src/Element/Text.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/Element/Text.tsx b/packages/app/src/Element/Text.tsx index 6078cf0c..6b865eac 100644 --- a/packages/app/src/Element/Text.tsx +++ b/packages/app/src/Element/Text.tsx @@ -36,7 +36,7 @@ export default function Text({ content, tags, creator, disableMedia, depth }: Te .map(f => { if (typeof f === "string") { return splitByUrl(f).map(a => { - if (a.match(/^(?:https?|(?:web\+)?nostr|magnet):/i)) { + if (a.match(/^(?:https?|(?:web\+)?nostr|magnet):/i) && a.toLowerCase() !== "nostr:npub") { if (disableMedia ?? false) { return ( e.stopPropagation()} target="_blank" rel="noreferrer" className="ext"> From 74bfa03227c6c59b01270f6e12f7a66d45ed7210 Mon Sep 17 00:00:00 2001 From: Sam Samskies Date: Wed, 19 Apr 2023 19:29:37 -0500 Subject: [PATCH 2/4] only render hyperlink if link is valid --- packages/app/src/Element/Text.tsx | 18 ++++++++++++++++-- packages/app/src/Util.ts | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/app/src/Element/Text.tsx b/packages/app/src/Element/Text.tsx index 6b865eac..6453de00 100644 --- a/packages/app/src/Element/Text.tsx +++ b/packages/app/src/Element/Text.tsx @@ -7,7 +7,7 @@ import * as unist from "unist"; import { HexKey, NostrPrefix } from "@snort/nostr"; import { MentionRegex, InvoiceRegex, HashtagRegex } from "Const"; -import { eventLink, hexToBech32, splitByUrl, unwrap } from "Util"; +import { eventLink, hexToBech32, splitByUrl, unwrap, validateNostrLink } from "Util"; import Invoice from "Element/Invoice"; import Hashtag from "Element/Hashtag"; import Mention from "Element/Mention"; @@ -36,7 +36,21 @@ export default function Text({ content, tags, creator, disableMedia, depth }: Te .map(f => { if (typeof f === "string") { return splitByUrl(f).map(a => { - if (a.match(/^(?:https?|(?:web\+)?nostr|magnet):/i) && a.toLowerCase() !== "nostr:npub") { + const validateLink = () => { + const normalizedStr = a.toLowerCase(); + + if (normalizedStr.startsWith("web+nostr") || normalizedStr.startsWith("nostr")) { + return validateNostrLink(normalizedStr); + } + + return ( + normalizedStr.startsWith("http") || + normalizedStr.startsWith("https") || + normalizedStr.startsWith("magnet") + ); + }; + + if (validateLink()) { if (disableMedia ?? false) { return ( e.stopPropagation()} target="_blank" rel="noreferrer" className="ext"> diff --git a/packages/app/src/Util.ts b/packages/app/src/Util.ts index 59f0bb31..4c1307da 100644 --- a/packages/app/src/Util.ts +++ b/packages/app/src/Util.ts @@ -491,6 +491,20 @@ export interface NostrLink { encode(): string; } +export function validateNostrLink(link: string): boolean { + try { + const parsedLink = parseNostrLink(link); + + if (!parsedLink) { + return false; + } + + return parsedLink.id.length === 64; + } catch { + return false; + } +} + export function parseNostrLink(link: string): NostrLink | undefined { const entity = link.startsWith("web+nostr:") || link.startsWith("nostr:") ? link.split(":")[1] : link; From b068c00b7f4d52e8fef118fce4c65a5b4a697b58 Mon Sep 17 00:00:00 2001 From: Sam Samskies Date: Wed, 19 Apr 2023 19:38:50 -0500 Subject: [PATCH 3/4] add missing : --- packages/app/src/Element/Text.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/app/src/Element/Text.tsx b/packages/app/src/Element/Text.tsx index 6453de00..6a173bab 100644 --- a/packages/app/src/Element/Text.tsx +++ b/packages/app/src/Element/Text.tsx @@ -39,14 +39,14 @@ export default function Text({ content, tags, creator, disableMedia, depth }: Te const validateLink = () => { const normalizedStr = a.toLowerCase(); - if (normalizedStr.startsWith("web+nostr") || normalizedStr.startsWith("nostr")) { + if (normalizedStr.startsWith("web+nostr:") || normalizedStr.startsWith("nostr:")) { return validateNostrLink(normalizedStr); } return ( - normalizedStr.startsWith("http") || - normalizedStr.startsWith("https") || - normalizedStr.startsWith("magnet") + normalizedStr.startsWith("http:") || + normalizedStr.startsWith("https:") || + normalizedStr.startsWith("magnet:") ); }; From 969284e47b830604f6fe15ad94b1a0dc860ae4ae Mon Sep 17 00:00:00 2001 From: Sam Samskies Date: Thu, 20 Apr 2023 15:01:29 -0500 Subject: [PATCH 4/4] fix logic and add unit tests --- packages/app/src/Util.test.ts | 23 ++++++++++++++++++++++- packages/app/src/Util.ts | 6 +++++- packages/app/src/setupTests.js | 4 ++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 packages/app/src/setupTests.js diff --git a/packages/app/src/Util.test.ts b/packages/app/src/Util.test.ts index 7745f888..dfae60e0 100644 --- a/packages/app/src/Util.test.ts +++ b/packages/app/src/Util.test.ts @@ -1,4 +1,4 @@ -import { splitByUrl, magnetURIDecode, getRelayName } from "./Util"; +import { splitByUrl, magnetURIDecode, getRelayName, validateNostrLink } from "./Util"; describe("splitByUrl", () => { it("should split a string by URLs", () => { @@ -90,3 +90,24 @@ describe("getRelayName", () => { expect(output).toEqual("relay.example2.com?broadcast=true"); }); }); + +describe("validateNostrLink", () => { + it("should return true for valid nostr links", () => { + [ + "nostr:npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg", + "web+nostr:npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg", + "nostr:note15449edq4qa5wzgqvh8td0q0dp6hwtes4pknsrm7eygeenhlj99xsq94wu9", + "nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p", + "nostr:nevent1qqs226juks2sw68pyqxtn4khs8ksath9uc2smfcpalvjyvuemlezjngrd87dq", + "nostr:naddr1qqzkjurnw4ksz9thwden5te0wfjkccte9ehx7um5wghx7un8qgs2d90kkcq3nk2jry62dyf50k0h36rhpdtd594my40w9pkal876jxgrqsqqqa28pccpzu", + ].forEach(link => { + expect(validateNostrLink(link)).toBe(true); + }); + }); + + it("should return false for invalid nostr links", () => { + ["nostr:npub", "web+nostr:npub", "nostr:nevent1xxx"].forEach(link => { + expect(validateNostrLink(link)).toBe(false); + }); + }); +}); diff --git a/packages/app/src/Util.ts b/packages/app/src/Util.ts index 4c1307da..8b115b96 100644 --- a/packages/app/src/Util.ts +++ b/packages/app/src/Util.ts @@ -499,7 +499,11 @@ export function validateNostrLink(link: string): boolean { return false; } - return parsedLink.id.length === 64; + if (parsedLink.type === NostrPrefix.PublicKey || parsedLink.type === NostrPrefix.Note) { + return parsedLink.id.length === 64; + } + + return true; } catch { return false; } diff --git a/packages/app/src/setupTests.js b/packages/app/src/setupTests.js new file mode 100644 index 00000000..0b5fd4d5 --- /dev/null +++ b/packages/app/src/setupTests.js @@ -0,0 +1,4 @@ +// @ts-expect-error - we have a folder called util so TS gets confused +import { TextEncoder, TextDecoder } from "util"; + +Object.assign(global, { TextDecoder, TextEncoder });