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,