diff --git a/package.json b/package.json index dd7f7d4a6..976267852 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "light-bolt11-decoder": "^3.2.0", "lodash.throttle": "^4.1.1", "match-sorter": "^8.0.0", - "nanoid": "^5.0.9", + "nanoid": "^5.1.0", "ngeohash": "^0.6.3", "nostr-idb": "^2.2.0", "nostr-signer-capacitor-plugin": "^0.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65619565e..b011370bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,25 +104,25 @@ importers: version: 0.7.2 applesauce-accounts: specifier: next - version: 0.0.0-next-20250214174945(typescript@5.7.3) + version: 0.0.0-next-20250215041231(typescript@5.7.3) applesauce-content: specifier: next - version: 0.0.0-next-20250214174945(typescript@5.7.3) + version: 0.0.0-next-20250215041231(typescript@5.7.3) applesauce-core: specifier: next - version: 0.0.0-next-20250214174945(typescript@5.7.3) + version: 0.0.0-next-20250215041231(typescript@5.7.3) applesauce-factory: specifier: next - version: 0.0.0-next-20250214174945(typescript@5.7.3) + version: 0.0.0-next-20250215041231(typescript@5.7.3) applesauce-loaders: specifier: next - version: 0.0.0-next-20250214174945(typescript@5.7.3) + version: 0.0.0-next-20250215041231(typescript@5.7.3) applesauce-react: specifier: next - version: 0.0.0-next-20250214174945(react-dom@19.0.0(react@19.0.0))(typescript@5.7.3) + version: 0.0.0-next-20250215041231(react-dom@19.0.0(react@19.0.0))(typescript@5.7.3) applesauce-signers: specifier: next - version: 0.0.0-next-20250214174945(typescript@5.7.3) + version: 0.0.0-next-20250215041231(typescript@5.7.3) bech32: specifier: ^2.0.0 version: 2.0.0 @@ -211,8 +211,8 @@ importers: specifier: ^8.0.0 version: 8.0.0 nanoid: - specifier: ^5.0.9 - version: 5.0.9 + specifier: ^5.1.0 + version: 5.1.0 ngeohash: specifier: ^0.6.3 version: 0.6.3 @@ -2189,26 +2189,26 @@ packages: engines: {node: '>=8.0.0'} hasBin: true - applesauce-accounts@0.0.0-next-20250214174945: - resolution: {integrity: sha512-kYjSmWeRo/sy8VW6FZiOYo7A8mIzVVLrJRTUoJfC9oASWmDG+Z5k4K6ECRwniFM0XQaootFyhUQH4AzBsI60rw==} + applesauce-accounts@0.0.0-next-20250215041231: + resolution: {integrity: sha512-v5isJLkBdqOqmcDA7aGh8mx5Umrmo07+2WVMJMnwEBCy6gGy7HHWYZtxzoL/k2Cq6m+wnYE9X6aroq6fdFCQIA==} - applesauce-content@0.0.0-next-20250214174945: - resolution: {integrity: sha512-Zm34S6ls8YLNLLn+sLTxMANzuTHucZz7yt50QsJyo8ns75DYoX4SrwWbAltzKWUAPyB68och5gy6WsEZANjf3Q==} + applesauce-content@0.0.0-next-20250215041231: + resolution: {integrity: sha512-1V2vhqBPFDPOJRpz9XC2/Xno4D0tHxHic9VcspxUO/rtuYvwTSAIdSNje39admpd3r++3z6/W4WLy8KFcxYmaA==} - applesauce-core@0.0.0-next-20250214174945: - resolution: {integrity: sha512-kyNj6If4oNREHaH+moIf4+l7WHw6dqMqeFoY7AwPA2rSnIFMEFojrXZkW7pPVmk3zI4S50ptEHPSaQTPQiBjdw==} + applesauce-core@0.0.0-next-20250215041231: + resolution: {integrity: sha512-VjHgDbF3UQhLQhfTVZwWgSys2WQ0WpeGouRIYC38X8C7NSU7utoQ3iYwnQbqHnHee6QeO4SOI23miF0CxdUigA==} - applesauce-factory@0.0.0-next-20250214174945: - resolution: {integrity: sha512-LpMgHWuTVLfSK7izsx5ZhUCnLSqF31BbflfRWgv5LpHxhOvgc5XFMsY76yrn1lFomNIATkxg8KkjdOCAvxNaCw==} + applesauce-factory@0.0.0-next-20250215041231: + resolution: {integrity: sha512-s0AuA1JmO7RZAfDxMcwmmhsazpwPfdGkHlsj2YYRGQo7maQBINCqJeo3pattuWSiuuW8FX1FH7DpfIFgaaHVzA==} - applesauce-loaders@0.0.0-next-20250214174945: - resolution: {integrity: sha512-mFh6kk9zWHlz+fN++cwco2ftBBWXuxZn/uGBM3EfD9lWFrPvs8CVfVYfDmwiRVNqF4/qL2ce9tJsB0w+2aVEOQ==} + applesauce-loaders@0.0.0-next-20250215041231: + resolution: {integrity: sha512-3+yXYXmdwJn1BQY5mpkp2ikdUW8BhqAxA6kdyrd7qymfIA2lTasSKk0lbBhhX5vidJWXRX1dpeP7BmlTAF1IGg==} - applesauce-react@0.0.0-next-20250214174945: - resolution: {integrity: sha512-O6xg+i04YNi2xH4K/dCapsdhMsXzbf0gMkGrq/K4TkAVfkYhBBYtj+vCySkPfJvcnp6LyGU5CC/3sFOlSiF+YQ==} + applesauce-react@0.0.0-next-20250215041231: + resolution: {integrity: sha512-HxDmEub+OkK33nVRvpuHKt4vIId9mB4AmXPabzCzWsLL0ZqaeP6xE4k7RB4YmJdLLbcLIres7u7Tu6bS2UNqHQ==} - applesauce-signers@0.0.0-next-20250214174945: - resolution: {integrity: sha512-qqJ1tCLEq9GPJ644ONeK7v/cD91RS887wPSwnItJlf5VJjOTGY/BEfSXNtpXaoXrSPiJ7SM1wBN0IPCuPB8s1g==} + applesauce-signers@0.0.0-next-20250215041231: + resolution: {integrity: sha512-uzljOtXb17DxI409WulLE2bX8yktwghxBYZOlzmr9Kfe3iGJFCA0SQxtAoK/APrBCEd9fx5XAUTD6VWmisIy7A==} arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -4329,8 +4329,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.0.9: - resolution: {integrity: sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==} + nanoid@5.1.0: + resolution: {integrity: sha512-zDAl/llz8Ue/EblwSYwdxGBYfj46IM1dhjVi8dyp9LQffoIGxJEAHj2oeZ4uNcgycSRcQ83CnfcZqEJzVDLcDw==} engines: {node: ^18 || >=20} hasBin: true @@ -8421,24 +8421,24 @@ snapshots: dependencies: entities: 2.2.0 - applesauce-accounts@0.0.0-next-20250214174945(typescript@5.7.3): + applesauce-accounts@0.0.0-next-20250215041231(typescript@5.7.3): dependencies: '@noble/hashes': 1.7.1 - applesauce-signers: 0.0.0-next-20250214174945(typescript@5.7.3) - nanoid: 5.0.9 + applesauce-signers: 0.0.0-next-20250215041231(typescript@5.7.3) + nanoid: 5.1.0 nostr-tools: 2.10.4(typescript@5.7.3) rxjs: 7.8.1 transitivePeerDependencies: - supports-color - typescript - applesauce-content@0.0.0-next-20250214174945(typescript@5.7.3): + applesauce-content@0.0.0-next-20250215041231(typescript@5.7.3): dependencies: '@cashu/cashu-ts': 2.0.0-rc1 '@types/hast': 3.0.4 '@types/mdast': 4.0.4 '@types/unist': 3.0.3 - applesauce-core: 0.0.0-next-20250214174945(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250215041231(typescript@5.7.3) mdast-util-find-and-replace: 3.0.2 nostr-tools: 2.10.4(typescript@5.7.3) remark: 15.0.1 @@ -8449,34 +8449,34 @@ snapshots: - supports-color - typescript - applesauce-core@0.0.0-next-20250214174945(typescript@5.7.3): + applesauce-core@0.0.0-next-20250215041231(typescript@5.7.3): dependencies: '@scure/base': 1.2.4 debug: 4.4.0 fast-deep-equal: 3.1.3 hash-sum: 2.0.0 light-bolt11-decoder: 3.2.0 - nanoid: 5.0.9 + nanoid: 5.1.0 nostr-tools: 2.10.4(typescript@5.7.3) rxjs: 7.8.1 transitivePeerDependencies: - supports-color - typescript - applesauce-factory@0.0.0-next-20250214174945(typescript@5.7.3): + applesauce-factory@0.0.0-next-20250215041231(typescript@5.7.3): dependencies: - applesauce-content: 0.0.0-next-20250214174945(typescript@5.7.3) - applesauce-core: 0.0.0-next-20250214174945(typescript@5.7.3) - nanoid: 5.0.9 + applesauce-content: 0.0.0-next-20250215041231(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250215041231(typescript@5.7.3) + nanoid: 5.1.0 nostr-tools: 2.10.4(typescript@5.7.3) transitivePeerDependencies: - supports-color - typescript - applesauce-loaders@0.0.0-next-20250214174945(typescript@5.7.3): + applesauce-loaders@0.0.0-next-20250215041231(typescript@5.7.3): dependencies: - applesauce-core: 0.0.0-next-20250214174945(typescript@5.7.3) - nanoid: 5.0.9 + applesauce-core: 0.0.0-next-20250215041231(typescript@5.7.3) + nanoid: 5.1.0 nostr-tools: 2.10.4(typescript@5.7.3) rx-nostr: 3.5.0 rxjs: 7.8.1 @@ -8484,12 +8484,12 @@ snapshots: - supports-color - typescript - applesauce-react@0.0.0-next-20250214174945(react-dom@19.0.0(react@19.0.0))(typescript@5.7.3): + applesauce-react@0.0.0-next-20250215041231(react-dom@19.0.0(react@19.0.0))(typescript@5.7.3): dependencies: - applesauce-accounts: 0.0.0-next-20250214174945(typescript@5.7.3) - applesauce-content: 0.0.0-next-20250214174945(typescript@5.7.3) - applesauce-core: 0.0.0-next-20250214174945(typescript@5.7.3) - applesauce-factory: 0.0.0-next-20250214174945(typescript@5.7.3) + applesauce-accounts: 0.0.0-next-20250215041231(typescript@5.7.3) + applesauce-content: 0.0.0-next-20250215041231(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250215041231(typescript@5.7.3) + applesauce-factory: 0.0.0-next-20250215041231(typescript@5.7.3) nostr-tools: 2.10.4(typescript@5.7.3) observable-hooks: 4.2.4(react-dom@19.0.0(react@19.0.0))(react@18.3.1)(rxjs@7.8.1) react: 18.3.1 @@ -8499,14 +8499,14 @@ snapshots: - supports-color - typescript - applesauce-signers@0.0.0-next-20250214174945(typescript@5.7.3): + applesauce-signers@0.0.0-next-20250215041231(typescript@5.7.3): dependencies: '@noble/hashes': 1.7.1 '@noble/secp256k1': 1.7.1 '@scure/base': 1.2.4 - applesauce-core: 0.0.0-next-20250214174945(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250215041231(typescript@5.7.3) debug: 4.4.0 - nanoid: 5.0.9 + nanoid: 5.1.0 nostr-tools: 2.10.4(typescript@5.7.3) transitivePeerDependencies: - supports-color @@ -10998,7 +10998,7 @@ snapshots: nanoid@3.3.8: {} - nanoid@5.0.9: {} + nanoid@5.1.0: {} napi-build-utils@2.0.0: {} diff --git a/src/components/event-zap-modal/index.tsx b/src/components/event-zap-modal/index.tsx index 3949ad43a..3d5ebff58 100644 --- a/src/components/event-zap-modal/index.tsx +++ b/src/components/event-zap-modal/index.tsx @@ -11,7 +11,7 @@ import { import dayjs from "dayjs"; import { kinds } from "nostr-tools"; import { getObservableValue } from "applesauce-core/observable"; -import { getInboxes, getInvoice, getOutboxes, safeRelayUrls } from "applesauce-core/helpers"; +import { getInboxes, getInvoice, getOutboxes, mergeRelaySets } from "applesauce-core/helpers"; import { getZapSplits } from "applesauce-core/helpers/zap"; import { DraftNostrEvent, NostrEvent, isDTag } from "../../types/nostr-event"; @@ -89,7 +89,7 @@ export async function getPayRequestForPubkey( content: comment ?? "", tags: [ ["p", pubkey], - ["relays", ...unique(safeRelayUrls([...userInbox, ...eventRelays, ...outbox, ...additional]))], + ["relays", ...mergeRelaySets(userInbox, eventRelays, outbox, additional)], ["amount", String(amount)], ], }; diff --git a/src/components/timeline-page/generic-note-timeline/relay-recommendation.tsx b/src/components/timeline-page/generic-note-timeline/relay-recommendation.tsx deleted file mode 100644 index b49c7ba3e..000000000 --- a/src/components/timeline-page/generic-note-timeline/relay-recommendation.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Flex, Text } from "@chakra-ui/react"; - -import { NostrEvent } from "../../../types/nostr-event"; -import UserAvatar from "../../user/user-avatar"; -import UserLink from "../../user/user-link"; -import RelayCard from "../../../views/relays/components/relay-card"; -import { safeRelayUrl } from "../../../helpers/relay"; -import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref"; - -export default function RelayRecommendation({ event }: { event: NostrEvent }) { - const safeUrl = safeRelayUrl(event.content); - const ref = useEventIntersectionRef(event); - - return ( - - - - - Recommended relay: - - {safeUrl ? : event.content} - - ); -} diff --git a/src/components/timeline-page/generic-note-timeline/timeline-item.tsx b/src/components/timeline-page/generic-note-timeline/timeline-item.tsx index dfcdccb19..3f3166241 100644 --- a/src/components/timeline-page/generic-note-timeline/timeline-item.tsx +++ b/src/components/timeline-page/generic-note-timeline/timeline-item.tsx @@ -5,7 +5,6 @@ import { Box, Spinner } from "@chakra-ui/react"; import { ErrorBoundary } from "../../error-boundary"; import ReplyNote from "./reply-note"; import ShareEvent from "./share-event"; -import RelayRecommendation from "./relay-recommendation"; import { isReply } from "../../../helpers/nostr/event"; import { NostrEvent } from "../../../types/nostr-event"; import { FLARE_VIDEO_KIND } from "../../../helpers/nostr/video"; @@ -36,9 +35,6 @@ function TimelineItem({ event, visible, minHeight }: { event: NostrEvent; visibl case kinds.LiveEvent: content = ; break; - case kinds.RecommendRelay: - content = ; - break; case kinds.BadgeAward: content = ; break; diff --git a/src/const.ts b/src/const.ts index bd442432b..c858db2d9 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,18 +1,23 @@ -import { safeRelayUrls } from "applesauce-core/helpers"; import { EventFactoryClient } from "applesauce-factory"; +import { isSafeRelayURL } from "applesauce-core/helpers/relays"; +import { normalizeURL } from "applesauce-core/helpers"; import { kinds } from "nostr-tools"; -export const DEFAULT_SEARCH_RELAYS = safeRelayUrls([ +function normalizeRelayURLs(relays: string[]) { + return relays.filter(isSafeRelayURL).map(normalizeURL); +} + +export const DEFAULT_SEARCH_RELAYS = normalizeRelayURLs([ "wss://relay.nostr.band", "wss://search.nos.today", "wss://relay.noswhere.com", "wss://filter.nostr.wine", ]); -export const WIKI_RELAYS = safeRelayUrls(["wss://relay.wikifreedia.xyz/"]); -export const COMMON_CONTACT_RELAYS = safeRelayUrls(["wss://purplepag.es/"]); +export const WIKI_RELAYS = normalizeRelayURLs(["wss://relay.wikifreedia.xyz/"]); +export const COMMON_CONTACT_RELAYS = normalizeRelayURLs(["wss://purplepag.es/"]); -export const DEFAULT_SIGNAL_RELAYS = safeRelayUrls(["wss://nostrue.com/", "wss://relay.damus.io"]); -export const DEFAULT_NOSTR_CONNECT_RELAYS = safeRelayUrls(["wss://relay.nsec.app/"]); +export const DEFAULT_SIGNAL_RELAYS = normalizeRelayURLs(["wss://nostrue.com/", "wss://relay.damus.io"]); +export const DEFAULT_NOSTR_CONNECT_RELAYS = normalizeRelayURLs(["wss://relay.nsec.app/"]); export const DEFAULT_ICE_SERVERS: RTCIceServer[] = [ { @@ -33,7 +38,7 @@ export const DEFAULT_ICE_SERVERS: RTCIceServer[] = [ }, ]; -export const RECOMMENDED_READ_RELAYS = safeRelayUrls([ +export const RECOMMENDED_READ_RELAYS = normalizeRelayURLs([ "wss://relay.damus.io/", "wss://nostr.wine/", "wss://relay.snort.social/", @@ -41,12 +46,20 @@ export const RECOMMENDED_READ_RELAYS = safeRelayUrls([ "wss://purplerelay.com/", "wss://nostr.land/", ]); -export const RECOMMENDED_WRITE_RELAYS = safeRelayUrls([ +export const RECOMMENDED_WRITE_RELAYS = normalizeRelayURLs([ "wss://relay.damus.io/", "wss://nos.lol/", "wss://purplerelay.com/", ]); +export const JAPANESE_RELAYS = normalizeRelayURLs([ + "wss://r.kojira.io", + "wss://nrelay-jp.c-stellar.net", + "wss://nostr.fediverse.jp", + "wss://nostr.holybea.com", + "wss://relay-jp.nostr.wirednet.jp", +]); + export const NOSTR_CONNECT_PERMISSIONS = [ "get_public_key", "nip04_encrypt", diff --git a/src/helpers/nip07.ts b/src/helpers/nip07.ts index 527bb81d5..3bab9d71f 100644 --- a/src/helpers/nip07.ts +++ b/src/helpers/nip07.ts @@ -1,21 +1,24 @@ -import { safeRelayUrls } from "applesauce-core/helpers"; +import { normalizeURL } from "applesauce-core/helpers"; export async function getRelaysFromExt() { if (!window.nostr) throw new Error("Missing extension"); const read = new Set(); const write = new Set(); - const extRelays = (await window.nostr.getRelays?.()) ?? []; + const extRelays = (await window.nostr.getRelays?.()) ?? ([] as string[]); if (Array.isArray(extRelays)) { - const safeUrls = safeRelayUrls(extRelays); - for (const url of safeUrls) { + for (const url of extRelays) { read.add(url); write.add(url); } } else { - const readUrls = safeRelayUrls(Object.keys(extRelays).filter((url) => extRelays[url].read)); + const readUrls = Object.keys(extRelays) + .filter((url) => extRelays[url].read) + .map(normalizeURL); for (const url of readUrls) read.add(url); - const writeUrls = safeRelayUrls(Object.keys(extRelays).filter((url) => extRelays[url].write)); + const writeUrls = Object.keys(extRelays) + .filter((url) => extRelays[url].write) + .map(normalizeURL); for (const url of writeUrls) write.add(url); } diff --git a/src/helpers/nip19.ts b/src/helpers/nip19.ts index 1967f4985..66899f81b 100644 --- a/src/helpers/nip19.ts +++ b/src/helpers/nip19.ts @@ -1,13 +1,12 @@ import { nip19 } from "nostr-tools"; import { getPubkeyFromDecodeResult, isHexKey } from "applesauce-core/helpers"; - -import { safeRelayUrls } from "./relay"; +import { isSafeRelayURL } from "applesauce-core/helpers/relays"; export function safeDecode(str: string) { try { const result = nip19.decode(str); if ((result.type === "nevent" || result.type === "nprofile" || result.type === "naddr") && result.data.relays) - result.data.relays = safeRelayUrls(result.data.relays); + result.data.relays = result.data.relays.filter(isSafeRelayURL); return result; } catch (e) {} } diff --git a/src/helpers/nostr/lists.ts b/src/helpers/nostr/lists.ts index 5272ad680..f14fbb1f6 100644 --- a/src/helpers/nostr/lists.ts +++ b/src/helpers/nostr/lists.ts @@ -1,10 +1,10 @@ import dayjs from "dayjs"; import { EventTemplate, NostrEvent, kinds } from "nostr-tools"; import { isAddressPointerInList, isEventPointerInList, isProfilePointerInList } from "applesauce-core/helpers/lists"; +import { mergeRelaySets } from "applesauce-core/helpers"; import { PTag, isDTag, isPTag, isRTag } from "../../types/nostr-event"; import { getEventCoordinate, replaceOrAddSimpleTag } from "./event"; -import { safeRelayUrls } from "../relay"; export const USER_GROUPS_LIST_KIND = 10009; @@ -100,9 +100,9 @@ export function getReferencesFromList(event: NostrEvent | EventTemplate) { } /** @deprecated this should be moved to applesauce-core if its still needed */ -export function getRelaysFromList(event: NostrEvent | EventTemplate) { - if (event.kind === kinds.RelayList) return safeRelayUrls(event.tags.filter(isRTag).map((t) => t[1])); - else return safeRelayUrls(event.tags.filter((t) => t[0] === "relay" && t[1]).map((t) => t[1]) as string[]); +export function getRelaysFromList(event: NostrEvent | EventTemplate): string[] { + if (event.kind === kinds.RelayList) return mergeRelaySets(event.tags.filter(isRTag).map((t) => t[1])); + else return mergeRelaySets(event.tags.filter((t) => t[0] === "relay" && t[1]).map((t) => t[1])); } /** @deprecated */ diff --git a/src/helpers/nostr/mailbox.ts b/src/helpers/nostr/mailbox.ts index 243ff150a..c32dde609 100644 --- a/src/helpers/nostr/mailbox.ts +++ b/src/helpers/nostr/mailbox.ts @@ -1,16 +1,20 @@ import { kinds } from "nostr-tools"; import { DraftNostrEvent, NostrEvent, RTag, Tag, isRTag } from "../../types/nostr-event"; -import { safeRelayUrl } from "../relay"; import { cloneEvent } from "./event"; import { RelayMode } from "../../services/app-relays"; +import { isSafeRelayURL } from "../../../../applesauce/packages/core/dist/helpers/relays"; +import { normalizeURL } from "applesauce-core/helpers"; -/** fixes or removes any bad r tags */ +/** + * fixes or removes any bad r tags + * @deprecated + */ export function cleanRTags(tags: Tag[]) { const newTags: Tag[] = []; for (const tag of tags) { if (tag[0] === "r") { - if (!tag[1]) continue; - const url = safeRelayUrl(tag[1]); + if (!tag[1] || !isSafeRelayURL(tag[1])) continue; + const url = normalizeURL(tag[1]); if (url) newTags.push(tag[2] ? ["r", url, tag[2]] : ["r", url]); } else newTags.push(tag); } @@ -34,10 +38,6 @@ export function createRelayTag(url: string, mode: RelayMode) { } } -export function getRelaysFromMailbox(list: NostrEvent | DraftNostrEvent): { url: string; mode: RelayMode }[] { - return cleanRTags(list.tags).filter(isRTag).map(parseRTag); -} - export function addRelayModeToMailbox(list: NostrEvent | undefined, relay: string, mode: RelayMode): DraftNostrEvent { const draft = cloneEvent(kinds.RelayList, list); draft.tags = cleanRTags(draft.tags); diff --git a/src/helpers/nostr/stream.ts b/src/helpers/nostr/stream.ts index 2e1b525f3..f3faed765 100644 --- a/src/helpers/nostr/stream.ts +++ b/src/helpers/nostr/stream.ts @@ -1,4 +1,4 @@ -import { getEventPointerFromETag, getTagValue, safeRelayUrl } from "applesauce-core/helpers"; +import { getEventPointerFromETag, getTagValue, isSafeRelayURL, normalizeURL } from "applesauce-core/helpers"; import { NostrEvent, isPTag } from "../../types/nostr-event"; import dayjs from "dayjs"; @@ -46,7 +46,9 @@ export function getStreamRelays(stream: NostrEvent) { if (tag[0] === "relays") { found = true; for (let i = 1; i < tag.length; i++) { - const relay = safeRelayUrl(tag[i]); + if (!isSafeRelayURL(tag[i])) continue; + + const relay = normalizeURL(tag[i]); if (relay && !relays.includes(relay)) relays.push(relay); } } diff --git a/src/helpers/relay.ts b/src/helpers/relay.ts index 14621cfbc..1a3f76083 100644 --- a/src/helpers/relay.ts +++ b/src/helpers/relay.ts @@ -9,52 +9,6 @@ export function getRelayVariations(relay: string) { } else return [relay, relay + "/"]; } -export function validateRelayURL(relay: string | URL) { - if (typeof relay === "string" && relay.includes(",ws")) throw new Error("Can not have multiple relays in one string"); - const url = typeof relay === "string" ? new URL(relay) : relay; - if (url.protocol !== "wss:" && url.protocol !== "ws:") throw new Error("Incorrect protocol"); - return url; -} -export function isValidRelayURL(relay: string | URL) { - try { - validateRelayURL(relay); - return true; - } catch (e) { - return false; - } -} - -/** @deprecated */ -export function normalizeRelayURL(relayUrl: string) { - const url = validateRelayURL(relayUrl); - url.pathname = url.pathname.replace(/\/+/g, "/"); - if ((url.port === "80" && url.protocol === "ws:") || (url.port === "443" && url.protocol === "wss:")) url.port = ""; - url.searchParams.sort(); - url.hash = ""; - return url.toString(); -} - -/** @deprecated */ -export function safeNormalizeRelayURL(relayUrl: string) { - try { - return normalizeRelayURL(relayUrl); - } catch (e) { - return null; - } -} - -// TODO: move these to helpers/relay -export function safeRelayUrl(relayUrl: string | URL) { - try { - return validateRelayURL(relayUrl).toString(); - } catch (e) { - return null; - } -} -export function safeRelayUrls(urls: Iterable): string[] { - return Array.from(urls).map(safeRelayUrl).filter(Boolean) as string[]; -} - export function splitNostrFilterByPubkeys( filter: Filter | Filter[], relayPubkeyMap: Record, @@ -134,16 +88,3 @@ const connectionStateSortOrder: ConnectionState[] = [ export function getConnectionStateSort(state: ConnectionState) { return connectionStateSortOrder.indexOf(state); } - -/** @deprecated use mergeRelaySets from applesauce-core */ -export function mergeRelaySets(...sources: (Iterable | undefined)[]) { - const set = new Set(); - for (const src of sources) { - if (!src) continue; - for (const url of src) { - const safe = safeRelayUrl(url); - if (safe) set.add(safe); - } - } - return Array.from(set); -} diff --git a/src/providers/local/additional-relay-context.tsx b/src/providers/local/additional-relay-context.tsx index 0f6ac7072..77cc7276f 100644 --- a/src/providers/local/additional-relay-context.tsx +++ b/src/providers/local/additional-relay-context.tsx @@ -1,6 +1,6 @@ import React, { useContext } from "react"; import { unique } from "../../helpers/array"; -import { safeRelayUrls } from "../../helpers/relay"; +import { mergeRelaySets } from "applesauce-core/helpers"; export const RelayContext = React.createContext([]); @@ -18,7 +18,7 @@ export function AdditionalRelayProvider({ extend?: boolean; }) { const parentRelays = useAdditionalRelayContext(); - const safeUrls = safeRelayUrls(extend ? [...parentRelays, ...relays] : relays); + const safeUrls = extend ? mergeRelaySets(parentRelays, relays) : relays; return {children}; } diff --git a/src/queries/trusted-mints.ts b/src/queries/trusted-mints.ts index ba28158ec..7712880bd 100644 --- a/src/queries/trusted-mints.ts +++ b/src/queries/trusted-mints.ts @@ -1,5 +1,5 @@ import { Query } from "applesauce-core"; -import { safeRelayUrl } from "applesauce-core/helpers"; +import { isSafeRelayURL } from "applesauce-core/helpers"; import { map } from "rxjs/operators"; type TrustedMints = { @@ -27,9 +27,8 @@ export default function TrustedMintsQuery(pubkey: string): Query = combineLatest([accounts.active$]).pipe( - mergeMap(([account]) => { + switchMap(([account]) => { if (!account) return []; const timeline$ = queryStore @@ -164,7 +175,11 @@ const notifications$: Observable = combineLatest([accounts.a ], }) .pipe( - filter(t => t!== undefined), + // filter out undefined + filter((t) => t !== undefined), + // update timeline at 30fps + throttleTime(1000 / 30), + // trigger logs of extra events tap((timeline) => { // handle loading dependencies of each event for (const event of timeline) { @@ -179,13 +194,17 @@ const notifications$: Observable = combineLatest([accounts.a } } }), + // categorize events map((timeline) => timeline.map((e) => categorizeEvent(e, account.pubkey))), ); const mute$ = queryStore.createQuery(UserMuteQuery, account.pubkey); return combineLatest([timeline$, mute$]).pipe( + // filter events out by mutes map(([timeline, mutes]) => filterEvents(timeline, account.pubkey, mutes)), + // keep the observable hot for 5 minutes after its unsubscribed + share({ connector: () => new ReplaySubject(1), resetOnComplete: () => timer(5 * 60_000) }), ); }), share(), diff --git a/src/views/discovery/relays/components/relay-details.tsx b/src/views/discovery/relays/components/relay-details.tsx index 63c272473..605dd8d87 100644 --- a/src/views/discovery/relays/components/relay-details.tsx +++ b/src/views/discovery/relays/components/relay-details.tsx @@ -12,7 +12,6 @@ import { FlexProps, Heading, IconButton, - Spacer, Text, } from "@chakra-ui/react"; import { useContext } from "react"; @@ -24,13 +23,11 @@ import { getTagValue } from "../../../../helpers/nostr/event"; import DebugEventButton from "../../../../components/debug-modal/debug-event-button"; import SupportedNIPs from "../../../relays/components/supported-nips"; import RelayNotes from "../../../relays/relay-details/relay-notes"; -import { safeRelayUrl } from "../../../../helpers/relay"; import { ExternalLinkIcon } from "../../../../components/icons"; import PeopleListProvider from "../../../../providers/local/people-list-provider"; import { getPubkeysFromList } from "../../../../helpers/nostr/lists"; import UserAvatarLink from "../../../../components/user/user-avatar-link"; import UserName from "../../../../components/user/user-name"; -import UserDnsIdentity from "../../../../components/user/user-dns-identity"; import RelayFavicon from "../../../../components/relay-favicon"; export default function RelayStatusDetails({ event, ...props }: Omit & { event: NostrEvent }) { @@ -40,7 +37,7 @@ export default function RelayStatusDetails({ event, ...props }: Omit t[0] === "l" && t[2] === "nip11.version")?.[1]; - const url = identity ? safeRelayUrl(identity) : undefined; + const url = identity; // gather labels const misc: Record = {}; diff --git a/src/views/messages/chat.tsx b/src/views/messages/chat.tsx index 490b07754..57610ae7d 100644 --- a/src/views/messages/chat.tsx +++ b/src/views/messages/chat.tsx @@ -1,6 +1,7 @@ import { memo, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { Button, ButtonGroup, Flex, IconButton } from "@chakra-ui/react"; import { UNSAFE_DataRouterContext, useLocation, useNavigate } from "react-router-dom"; +import { mergeRelaySets } from "applesauce-core/helpers"; import { NostrEvent, kinds } from "nostr-tools"; import { ThreadIcon } from "../../components/icons"; @@ -24,8 +25,6 @@ import useRouterMarker from "../../hooks/use-router-marker"; import decryptionCacheService from "../../services/decryption-cache"; import UserDnsIdentityIcon from "../../components/user/user-dns-identity-icon"; import UserAvatarLink from "../../components/user/user-avatar-link"; -import ContainedSimpleView from "../../components/layout/presets/contained-simple-view"; -import { mergeRelaySets } from "../../helpers/relay"; import SimpleView from "../../components/layout/presets/simple-view"; /** This is broken out from DirectMessageChatPage for performance reasons. Don't use outside of file */ diff --git a/src/views/relays/components/add-custom-modal.tsx b/src/views/relays/components/add-custom-modal.tsx deleted file mode 100644 index 3a0ab7ee3..000000000 --- a/src/views/relays/components/add-custom-modal.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { useState } from "react"; -import { - Box, - Button, - ButtonGroup, - Code, - Flex, - FormControl, - FormLabel, - IconButton, - Input, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, - ModalProps, - Text, - useDisclosure, -} from "@chakra-ui/react"; -import { useDebounce } from "react-use"; - -import { useRelayInfo } from "../../../hooks/use-relay-info"; -import UserAvatar from "../../../components/user/user-avatar"; -import UserLink from "../../../components/user/user-link"; -import UserDnsIdentity from "../../../components/user/user-dns-identity"; -import { CodeIcon } from "../../../components/icons"; -import { Metadata } from "./relay-card"; -import { safeRelayUrl } from "../../../helpers/relay"; - -function RelayDetails({ url, debug }: { url: string; debug?: boolean }) { - const { info } = useRelayInfo(url); - - if (!info) return null; - - return ( - - {info.name} - {url} - {info.pubkey && ( - - Owner: - - - - - )} - {info.supported_nips?.join(", ")} - {debug && ( - - {JSON.stringify(info, null, 2)} - - )} - - ); -} - -export default function AddCustomRelayModal({ - onSubmit, - ...props -}: { onSubmit: (relay: string) => void } & Omit) { - const [url, setUrl] = useState(""); - const [safeUrl, setSafeUrl] = useState(); - const showDebug = useDisclosure(); - - useDebounce(() => setSafeUrl(safeRelayUrl(url) ?? undefined), 1000, [url]); - - return ( - - - - Custom Relay - - - - Relay URL - setUrl(e.target.value)} - /> - - - {safeUrl && } - - - - - {safeUrl && ( - } aria-label="Show JSON" onClick={showDebug.onToggle} variant="ghost" /> - )} - - - - - - - ); -} diff --git a/src/views/relays/relay-details/index.tsx b/src/views/relays/relay-details/index.tsx index e77e4d10f..cec0389f2 100644 --- a/src/views/relays/relay-details/index.tsx +++ b/src/views/relays/relay-details/index.tsx @@ -13,6 +13,7 @@ import { Tag, useDisclosure, } from "@chakra-ui/react"; +import { isSafeRelayURL, normalizeURL } from "applesauce-core/helpers"; import { useRelayInfo } from "../../../hooks/use-relay-info"; import { RelayDebugButton, RelayMetadata } from "../components/relay-card"; @@ -23,7 +24,6 @@ import RelayNotes from "./relay-notes"; import PeopleListProvider from "../../../providers/local/people-list-provider"; import PeopleListSelection from "../../../components/people-list-selection/people-list-selection"; import RelayFavicon from "../../../components/relay-favicon"; -import { safeRelayUrl } from "../../../helpers/relay"; import RelayUsersTab from "./relay-users"; const RelayDetailsTab = lazy(() => import("./relay-details")); @@ -108,12 +108,11 @@ export default function RelayDetailsView() { const { relay } = useParams(); if (!relay) return <>No relay url; - const safeUrl = safeRelayUrl(relay); - if (!safeUrl) return <>Bad relay url; + if (!isSafeRelayURL(relay)) return <>Bad relay url; return ( - + ); } diff --git a/src/views/settings/bakery/network/gossip.tsx b/src/views/settings/bakery/network/gossip.tsx index 8bee8d6ca..3b4494dbc 100644 --- a/src/views/settings/bakery/network/gossip.tsx +++ b/src/views/settings/bakery/network/gossip.tsx @@ -1,12 +1,12 @@ import { CloseIcon } from "@chakra-ui/icons"; -import { Button, Code, Flex, Heading, IconButton, Input, Link, Select, Switch, Text } from "@chakra-ui/react"; +import { Button, Flex, Heading, IconButton, Input, Link, Select, Switch, Text } from "@chakra-ui/react"; import { useForm } from "react-hook-form"; -import { safeRelayUrl } from "applesauce-core/helpers"; import { useObservable } from "applesauce-react/hooks"; import useAsyncErrorHandler from "../../../../hooks/use-async-error-handler"; import { controlApi$ } from "../../../../services/bakery"; import RelayFavicon from "../../../../components/relay-favicon"; +import { isSafeRelayURL, normalizeURL } from "applesauce-core/helpers"; function BroadcastRelay({ relay }: { relay: string }) { const controlApi = useObservable(controlApi$); @@ -44,8 +44,10 @@ function AddRelayForm() { const submit = handleSubmit((values) => { if (!config) return; - const url = safeRelayUrl(values.url); - if (url) controlApi?.setConfigField("gossipBroadcastRelays", [...config.gossipBroadcastRelays, url]); + if (!isSafeRelayURL(values.url)) return; + + const url = normalizeURL(values.url); + controlApi?.setConfigField("gossipBroadcastRelays", [...config.gossipBroadcastRelays, url]); reset(); }); diff --git a/src/views/settings/relays/add-relay-form.tsx b/src/views/settings/relays/add-relay-form.tsx index 3d7dd7ab8..401d8d8fc 100644 --- a/src/views/settings/relays/add-relay-form.tsx +++ b/src/views/settings/relays/add-relay-form.tsx @@ -1,8 +1,9 @@ import { Button, Flex, FlexProps, useToast } from "@chakra-ui/react"; import { useForm } from "react-hook-form"; -import { safeRelayUrl } from "../../../helpers/relay"; import { RelayUrlInput } from "../../../components/relay-url-input"; +import { isSafeRelayURL } from "../../../../../applesauce/packages/core/dist/helpers/relays"; +import { normalizeURL } from "applesauce-core/helpers"; export default function AddRelayForm({ onSubmit, @@ -21,9 +22,8 @@ export default function AddRelayForm({ const submit = handleSubmit(async (values) => { try { - const url = safeRelayUrl(values.url); - if (!url) return; - await onSubmit(url); + if (!isSafeRelayURL(values.url)) return; + await onSubmit(normalizeURL(values.url)); reset(); } catch (error) { if (error instanceof Error) toast({ status: "error", description: error.message }); diff --git a/src/views/settings/relays/index.tsx b/src/views/settings/relays/index.tsx index cc3d91009..d435e9515 100644 --- a/src/views/settings/relays/index.tsx +++ b/src/views/settings/relays/index.tsx @@ -4,7 +4,7 @@ import { WarningIcon } from "@chakra-ui/icons"; import { IdentityStatus } from "applesauce-loaders/helpers/dns-identity"; import { mergeRelaySets } from "applesauce-core/helpers"; -import { RECOMMENDED_READ_RELAYS, RECOMMENDED_WRITE_RELAYS } from "../../../const"; +import { JAPANESE_RELAYS, RECOMMENDED_READ_RELAYS, RECOMMENDED_WRITE_RELAYS } from "../../../const"; import AddRelayForm from "./add-relay-form"; import { useReadRelays, useWriteRelays } from "../../../hooks/use-client-relays"; import { useActiveAccount } from "applesauce-react/hooks"; @@ -12,21 +12,12 @@ import RelayControl from "./relay-control"; import { getRelaysFromExt } from "../../../helpers/nip07"; import { useUserDNSIdentity } from "../../../hooks/use-user-dns-identity"; import useUserContactRelays from "../../../hooks/use-user-contact-relays"; -import { safeRelayUrls } from "../../../helpers/relay"; import HoverLinkOverlay from "../../../components/hover-link-overlay"; import SimpleView from "../../../components/layout/presets/simple-view"; import localSettings from "../../../services/local-settings"; import { addAppRelay, RelayMode } from "../../../services/app-relays"; import useUserMailboxes from "../../../hooks/use-user-mailboxes"; -const JAPANESE_RELAYS = safeRelayUrls([ - "wss://r.kojira.io", - "wss://nrelay-jp.c-stellar.net", - "wss://nostr.fediverse.jp", - "wss://nostr.holybea.com", - "wss://relay-jp.nostr.wirednet.jp", -]); - function RelaySetCard({ label, read, write }: { label: string; read: Iterable; write: Iterable }) { const handleClick = useCallback((e) => { e.preventDefault(); diff --git a/src/views/signin/address/create.tsx b/src/views/signin/address/create.tsx index 571f35df2..5def189b2 100644 --- a/src/views/signin/address/create.tsx +++ b/src/views/signin/address/create.tsx @@ -18,9 +18,10 @@ import { import { useNavigate } from "react-router-dom"; import { NostrEvent } from "nostr-tools"; import { NostrConnectSigner } from "applesauce-signers/signers/nostr-connect-signer"; -import { parseNIP05Address, ProfileContent, safeParse } from "applesauce-core/helpers"; +import { mergeRelaySets, parseNIP05Address, ProfileContent, safeParse } from "applesauce-core/helpers"; import { useAccountManager } from "applesauce-react/hooks"; import { NostrConnectAccount } from "applesauce-accounts/accounts"; +import { IdentityStatus } from "applesauce-loaders/helpers/dns-identity"; import { NOSTR_CONNECT_PERMISSIONS } from "../../../const"; import useNip05Providers from "../../../hooks/use-nip05-providers"; @@ -30,9 +31,7 @@ import { MetadataAvatar } from "../../../components/user/user-avatar"; import { ErrorBoundary } from "../../../components/error-boundary"; import dnsIdentityLoader from "../../../services/dns-identity-loader"; import useUserProfile from "../../../hooks/use-user-profile"; -import { safeRelayUrls } from "../../../helpers/relay"; import { createNostrConnectConnection } from "../../../classes/nostr-connect-connection"; -import { IdentityStatus } from "applesauce-loaders/helpers/dns-identity"; function ProviderCard({ onClick, provider }: { onClick: () => void; provider: NostrEvent }) { const metadata = JSON.parse(provider.content) as ProfileContent; @@ -96,7 +95,7 @@ export default function LoginNostrAddressCreate() { if (identity.name !== "_") throw new Error("Provider does not own the domain"); if (!identity.hasNip46) throw new Error("Provider does not support NIP-46"); - const relays = safeRelayUrls(identity.nip46Relays || identity.relays || []); + const relays = mergeRelaySets(identity.nip46Relays || identity.relays || []); if (relays.length === 0) throw new Error("Cant find providers relays"); const signer = new NostrConnectSigner({ diff --git a/src/views/signin/address/index.tsx b/src/views/signin/address/index.tsx index c1bdf6494..56806f87a 100644 --- a/src/views/signin/address/index.tsx +++ b/src/views/signin/address/index.tsx @@ -5,13 +5,13 @@ import { NostrConnectSigner } from "applesauce-signers/signers/nostr-connect-sig import { useAccountManager } from "applesauce-react/hooks"; import { NostrConnectAccount, ReadonlyAccount } from "applesauce-accounts/accounts"; import { Identity, IdentityStatus } from "applesauce-loaders/helpers/dns-identity"; +import { mergeRelaySets } from "applesauce-core/helpers"; import { ReadonlySigner } from "applesauce-signers"; import { useDebounce } from "react-use"; import dnsIdentityLoader from "../../../services/dns-identity-loader"; import { CheckIcon } from "../../../components/icons"; import { NOSTR_CONNECT_PERMISSIONS } from "../../../const"; -import { safeRelayUrls } from "../../../helpers/relay"; import { getMatchSimpleEmail } from "../../../helpers/regexp"; import QRCodeScannerButton from "../../../components/qr-code/qr-code-scanner-button"; import { createNostrConnectConnection } from "../../../classes/nostr-connect-connection"; @@ -46,7 +46,7 @@ export default function LoginNostrAddressView() { try { if (nip05.hasNip46 && nip05.pubkey) { setLoading("Connecting..."); - const relays = safeRelayUrls(nip05.nip46Relays || nip05.relays || []); + const relays = mergeRelaySets(nip05.nip46Relays || nip05.relays || []); const signer = new NostrConnectSigner({ pubkey: nip05.pubkey, relays,