mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-28 18:53:47 +01:00
overhaul core relay code
This commit is contained in:
parent
078073bbed
commit
91f4c7c92e
.changeset
src
classes
components
common-menu-items
debug-modals
embed-event
embed-types
event-reactions
event-zap-modal
note
relay-selection
user-follow-button.tsxhelpers
hooks
use-app-settings.tsuse-channel-metadata.tsuse-client-relays.tsuse-event-bookmark-actions.tsuse-event-reactions.tsuse-event-zaps.tsuse-events-reactions.tsuse-favorite-emoji-packs.tsuse-replaceable-event.tsuse-replaceable-events.tsuse-shareable-profile-id.tsuse-single-event.tsuse-single-events.tsuse-stream-goal.tsuse-thread-timeline-loader.tsuse-timeline-loader.tsuse-user-communities-list.tsuse-user-contact-list.tsuse-user-lists.tsuse-user-mailboxes.tsuse-user-metadata.tsuse-user-mute-actions.tsuse-user-mute-list.tsuse-user-mute-lists.tsuse-user-network.tsuse-user-profile-badges.tsuse-user-relays.ts
providers
services
channel-metadata.tsclient-relays.tsevent-reactions.tsevent-zaps.tsrelay-scoreboard.tsreplaceable-event-requester.ts
settings
single-event.tsuser-contacts.tsuser-mailboxes.tsuser-metadata.tsviews
channels/components
communities
community
dms
dvm-feed
emoji-packs
lists/components
relays
signup
streams
thread
tools
torrents
user
5
.changeset/tall-shrimps-obey.md
Normal file
5
.changeset/tall-shrimps-obey.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Overhaul core relay code
|
@ -23,9 +23,9 @@ export default class NostrPublishAction {
|
||||
|
||||
private remaining = new Set<Relay>();
|
||||
|
||||
constructor(label: string, relays: string[], event: NostrEvent, timeout: number = 5000) {
|
||||
constructor(label: string, relays: Iterable<string>, event: NostrEvent, timeout: number = 5000) {
|
||||
this.label = label;
|
||||
this.relays = relays;
|
||||
this.relays = Array.from(relays);
|
||||
this.event = event;
|
||||
|
||||
for (const url of relays) {
|
||||
|
@ -21,9 +21,9 @@ export default class NostrRequest {
|
||||
onComplete = createDefer<void>();
|
||||
seenEvents = new Set<string>();
|
||||
|
||||
constructor(relayUrls: string[], timeout?: number) {
|
||||
constructor(relayUrls: Iterable<string>, timeout?: number) {
|
||||
this.id = nanoid();
|
||||
this.relays = new Set(relayUrls.map((url) => relayPoolService.requestRelay(url)));
|
||||
this.relays = new Set(Array.from(relayUrls).map((url) => relayPoolService.requestRelay(url)));
|
||||
|
||||
for (const relay of this.relays) {
|
||||
relay.onEOSE.subscribe(this.handleEOSE, this);
|
||||
|
47
src/classes/relay-set.ts
Normal file
47
src/classes/relay-set.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { safeRelayUrl, safeRelayUrls } from "../helpers/relay";
|
||||
import relayPoolService from "../services/relay-pool";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { RelayMode } from "./relay";
|
||||
|
||||
export default class RelaySet extends Set<string> {
|
||||
get urls() {
|
||||
return Array.from(this);
|
||||
}
|
||||
getRelays() {
|
||||
return this.urls.map((url) => relayPoolService.requestRelay(url, false));
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new RelaySet(this);
|
||||
}
|
||||
merge(src: Iterable<string>): this {
|
||||
for (const url of src) this.add(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
static from(...sources: (Iterable<string> | undefined)[]) {
|
||||
const set = new RelaySet();
|
||||
for (const src of sources) {
|
||||
if (!src) continue;
|
||||
for (const url of src) {
|
||||
const safe = safeRelayUrl(url);
|
||||
if (safe) set.add(safe);
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
static fromNIP65Event(event: NostrEvent, mode: RelayMode = RelayMode.ALL) {
|
||||
const set = new RelaySet();
|
||||
for (const tag of event.tags) {
|
||||
if (tag[0] === "r" && tag[1]) {
|
||||
const url = safeRelayUrl(tag[1]);
|
||||
if (!url) continue;
|
||||
if (tag[2] === "write" && mode & RelayMode.WRITE) set.add(url);
|
||||
else if (tag[2] === "read" && mode & RelayMode.READ) set.add(url);
|
||||
else set.add(url);
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
@ -24,7 +24,6 @@ export type IncomingEOSE = {
|
||||
subId: string;
|
||||
relay: Relay;
|
||||
};
|
||||
// NIP-20
|
||||
export type IncomingCommandResult = {
|
||||
type: "OK";
|
||||
eventId: string;
|
||||
@ -39,7 +38,6 @@ export enum RelayMode {
|
||||
WRITE = 2,
|
||||
ALL = 1 | 2,
|
||||
}
|
||||
export type RelayConfig = { url: string; mode: RelayMode };
|
||||
|
||||
const CONNECTION_TIMEOUT = 1000 * 30;
|
||||
|
||||
@ -53,7 +51,6 @@ export default class Relay {
|
||||
onEOSE = new Subject<IncomingEOSE>(undefined, false);
|
||||
onCommandResult = new Subject<IncomingCommandResult>(undefined, false);
|
||||
ws?: WebSocket;
|
||||
mode: RelayMode = RelayMode.ALL;
|
||||
|
||||
private connectionTimer?: () => void;
|
||||
private ejectTimer?: () => void;
|
||||
@ -61,9 +58,8 @@ export default class Relay {
|
||||
private subscriptionResTimer = new Map<string, () => void>();
|
||||
private queue: NostrOutgoingMessage[] = [];
|
||||
|
||||
constructor(url: string, mode: RelayMode = RelayMode.ALL) {
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
open() {
|
||||
@ -112,16 +108,14 @@ export default class Relay {
|
||||
this.ws.onmessage = this.handleMessage.bind(this);
|
||||
}
|
||||
send(json: NostrOutgoingMessage) {
|
||||
if (this.mode & RelayMode.WRITE) {
|
||||
if (this.connected) {
|
||||
this.ws?.send(JSON.stringify(json));
|
||||
if (this.connected) {
|
||||
this.ws?.send(JSON.stringify(json));
|
||||
|
||||
// record start time
|
||||
if (json[0] === "REQ" || json[0] === "COUNT") {
|
||||
this.startSubResTimer(json[1]);
|
||||
}
|
||||
} else this.queue.push(json);
|
||||
}
|
||||
// record start time
|
||||
if (json[0] === "REQ" || json[0] === "COUNT") {
|
||||
this.startSubResTimer(json[1]);
|
||||
}
|
||||
} else this.queue.push(json);
|
||||
}
|
||||
close() {
|
||||
this.ws?.close();
|
||||
@ -172,8 +166,6 @@ export default class Relay {
|
||||
// skip empty events
|
||||
if (!event.data) return;
|
||||
|
||||
if (!(this.mode & RelayMode.READ)) return;
|
||||
|
||||
try {
|
||||
const data: RawIncomingNostrEvent = JSON.parse(event.data);
|
||||
const type = data[0];
|
||||
|
@ -1,6 +1,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import { Debugger } from "debug";
|
||||
import { Filter } from "nostr-tools";
|
||||
import _throttle from "lodash.throttle";
|
||||
|
||||
import { NostrEvent, isATag, isETag } from "../types/nostr-event";
|
||||
import { NostrRequestFilter, RelayQueryMap } from "../types/nostr-query";
|
||||
@ -129,13 +130,14 @@ export default class TimelineLoader {
|
||||
this.subscription.onEvent.subscribe(this.handleEvent, this);
|
||||
|
||||
// update the timeline when there are new events
|
||||
this.events.onEvent.subscribe(this.updateTimeline, this);
|
||||
this.events.onDelete.subscribe(this.updateTimeline, this);
|
||||
this.events.onClear.subscribe(this.updateTimeline, this);
|
||||
this.events.onEvent.subscribe(this.throttleUpdateTimeline, this);
|
||||
this.events.onDelete.subscribe(this.throttleUpdateTimeline, this);
|
||||
this.events.onClear.subscribe(this.throttleUpdateTimeline, this);
|
||||
|
||||
deleteEventService.stream.subscribe(this.handleDeleteEvent, this);
|
||||
}
|
||||
|
||||
private throttleUpdateTimeline = _throttle(this.updateTimeline, 10);
|
||||
private updateTimeline() {
|
||||
if (this.eventFilter) {
|
||||
const filter = this.eventFilter;
|
||||
|
@ -35,7 +35,7 @@ export default function PinNoteMenuItem({ event }: { event: NostrEvent }) {
|
||||
else draft = listAddEvent(draft, event.id);
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction(label, clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction(label, clientRelaysService.outbox.urls, signed);
|
||||
setLoading(false);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ status: "error", description: e.message });
|
||||
|
@ -16,7 +16,7 @@ export default function NoteDebugModal({ event, ...props }: { event: NostrEvent
|
||||
const [loading, setLoading] = useState(false);
|
||||
const broadcast = useCallback(() => {
|
||||
setLoading(true);
|
||||
const relays = clientRelaysService.getWriteUrls();
|
||||
const relays = clientRelaysService.outbox.urls;
|
||||
const pub = new NostrPublishAction("Broadcast", relays, event, 5000);
|
||||
pub.onComplete.then(() => setLoading(false));
|
||||
}, []);
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { Suspense, lazy } from "react";
|
||||
import type { DecodeResult } from "nostr-tools/lib/types/nip19";
|
||||
import { Button, CardProps, Spinner } from "@chakra-ui/react";
|
||||
import { kinds, nip19 } from "nostr-tools";
|
||||
import { CardProps, Spinner } from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import EmbeddedNote from "./event-types/embedded-note";
|
||||
import useSingleEvent from "../../hooks/use-single-event";
|
||||
import { NoteLink } from "../note-link";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { STREAM_CHAT_MESSAGE_KIND, STREAM_KIND } from "../../helpers/nostr/stream";
|
||||
import { GOAL_KIND } from "../../helpers/nostr/goal";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Link, Text, Tooltip } from "@chakra-ui/react";
|
||||
import { Link, Tooltip } from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
|
||||
import { EmbedableContent, embedJSX } from "../../helpers/embeds";
|
||||
|
@ -24,7 +24,7 @@ export function useAddReaction(event: NostrEvent, grouped: ReactionGroup[]) {
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
if (signed) {
|
||||
const writeRelays = clientRelaysService.getWriteUrls();
|
||||
const writeRelays = clientRelaysService.outbox.urls;
|
||||
new NostrPublishAction("Reaction", writeRelays, signed);
|
||||
eventReactionsService.handleEvent(signed);
|
||||
}
|
||||
|
@ -15,11 +15,10 @@ import { DraftNostrEvent, NostrEvent, isDTag } from "../../types/nostr-event";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
import { getZapSplits } from "../../helpers/nostr/zaps";
|
||||
import { unique } from "../../helpers/array";
|
||||
import { RelayMode } from "../../classes/relay";
|
||||
import relayScoreboardService from "../../services/relay-scoreboard";
|
||||
import { getEventCoordinate, isReplaceable } from "../../helpers/nostr/events";
|
||||
import { EmbedProps } from "../embed-event";
|
||||
import userRelaysService from "../../services/user-relays";
|
||||
import userMailboxesService from "../../services/user-mailboxes";
|
||||
import InputStep from "./input-step";
|
||||
import lnurlMetadataService from "../../services/lnurl-metadata";
|
||||
import userMetadataService from "../../services/user-metadata";
|
||||
@ -62,15 +61,10 @@ async function getPayRequestForPubkey(
|
||||
}
|
||||
|
||||
const userInbox = relayScoreboardService
|
||||
.getRankedRelays(
|
||||
userRelaysService
|
||||
.getRelays(pubkey)
|
||||
.value?.relays.filter((r) => r.mode & RelayMode.READ)
|
||||
.map((r) => r.url) ?? [],
|
||||
)
|
||||
.getRankedRelays(userMailboxesService.getMailboxes(pubkey).value?.inbox)
|
||||
.slice(0, 4);
|
||||
const eventRelays = event ? relayHintService.getEventRelayHints(event, 4) : [];
|
||||
const outbox = relayScoreboardService.getRankedRelays(clientRelaysService.getWriteUrls()).slice(0, 4);
|
||||
const outbox = relayScoreboardService.getRankedRelays(clientRelaysService.outbox.urls).slice(0, 4);
|
||||
const additional = relayScoreboardService.getRankedRelays(additionalRelays);
|
||||
|
||||
// create zap request
|
||||
|
@ -41,7 +41,7 @@ export default function AddReactionButton({
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
if (signed) {
|
||||
const writeRelays = clientRelaysService.getWriteUrls();
|
||||
const writeRelays = clientRelaysService.outbox.urls;
|
||||
new NostrPublishAction("Reaction", writeRelays, signed);
|
||||
eventReactionsService.handleEvent(signed);
|
||||
setPopover.off();
|
||||
|
@ -48,7 +48,7 @@ export default function BookmarkButton({ event, ...props }: { event: NostrEvent
|
||||
async (cords: string | string[]) => {
|
||||
if (!Array.isArray(cords)) return;
|
||||
|
||||
const writeRelays = clientRelaysService.getWriteUrls();
|
||||
const writeRelays = clientRelaysService.outbox.urls;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
|
@ -67,7 +67,7 @@ export default function RepostModal({
|
||||
]);
|
||||
}
|
||||
const signed = await requestSignature(draftRepost);
|
||||
const pub = new NostrPublishAction("Repost", clientRelaysService.getWriteUrls(), signed);
|
||||
const pub = new NostrPublishAction("Repost", clientRelaysService.outbox.urls, signed);
|
||||
await pub.onComplete;
|
||||
onClose();
|
||||
} catch (e) {
|
||||
|
@ -30,7 +30,7 @@ export default function NoteMenu({
|
||||
const translationsModal = useDisclosure();
|
||||
|
||||
const broadcast = useCallback(() => {
|
||||
const missingRelays = clientRelaysService.getWriteUrls();
|
||||
const missingRelays = clientRelaysService.outbox.urls;
|
||||
const pub = new NostrPublishAction("Broadcast", missingRelays, event, 5000);
|
||||
pub.onResult.subscribe((result) => {
|
||||
if (result.status) handleEventFromRelay(result.relay, event);
|
||||
|
@ -28,7 +28,7 @@ export default function NoteZapButton({ event, allowComment, showEventPreview, .
|
||||
|
||||
const onZapped = () => {
|
||||
onClose();
|
||||
eventZapsService.requestZaps(getEventUID(event), clientRelaysService.getReadUrls(), true);
|
||||
eventZapsService.requestZaps(getEventUID(event), clientRelaysService.inbox.urls, true);
|
||||
};
|
||||
|
||||
const total = totalZaps(zaps);
|
||||
|
@ -78,7 +78,7 @@ export default function RelaySelectionModal({
|
||||
/>
|
||||
<CheckboxGroup value={newSelected} onChange={(urls) => setSelected(urls.map(String))}>
|
||||
<Flex direction="column" gap="2" mb="2">
|
||||
{relays.map((url) => (
|
||||
{relays.urls.map((url) => (
|
||||
<Checkbox key={url} value={url}>
|
||||
<RelayFavicon relay={url} size="xs" /> {url}
|
||||
</Checkbox>
|
||||
@ -87,7 +87,7 @@ export default function RelaySelectionModal({
|
||||
</CheckboxGroup>
|
||||
|
||||
<ButtonGroup>
|
||||
<Button onClick={() => setSelected(relays)} size="sm">
|
||||
<Button onClick={() => setSelected(Array.from(relays))} size="sm">
|
||||
All
|
||||
</Button>
|
||||
<Button onClick={() => setSelected([])} size="sm">
|
||||
|
@ -51,7 +51,7 @@ function UsersLists({ pubkey }: { pubkey: string }) {
|
||||
async (cords: string | string[]) => {
|
||||
if (!Array.isArray(cords)) return;
|
||||
|
||||
const writeRelays = clientRelaysService.getWriteUrls();
|
||||
const writeRelays = clientRelaysService.outbox.urls;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
@ -127,13 +127,13 @@ export const UserFollowButton = ({ pubkey, showLists, ...props }: UserFollowButt
|
||||
const handleFollow = useAsyncErrorHandler(async () => {
|
||||
const draft = listAddPerson(contacts || createEmptyContactList(), pubkey);
|
||||
const signed = await requestSignature(draft);
|
||||
const pub = new NostrPublishAction("Follow", clientRelaysService.getWriteUrls(), signed);
|
||||
const pub = new NostrPublishAction("Follow", clientRelaysService.outbox.urls, signed);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
}, [contacts, requestSignature]);
|
||||
const handleUnfollow = useAsyncErrorHandler(async () => {
|
||||
const draft = listRemovePerson(contacts || createEmptyContactList(), pubkey);
|
||||
const signed = await requestSignature(draft);
|
||||
const pub = new NostrPublishAction("Unfollow", clientRelaysService.getWriteUrls(), signed);
|
||||
const pub = new NostrPublishAction("Unfollow", clientRelaysService.outbox.urls, signed);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
}, [contacts, requestSignature]);
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { getPublicKey, nip19 } from "nostr-tools";
|
||||
import { NostrEvent, Tag, isATag, isDTag, isETag, isPTag } from "../types/nostr-event";
|
||||
import { isReplaceable } from "./nostr/events";
|
||||
import relayHintService from "../services/event-relay-hint";
|
||||
import { safeRelayUrls } from "./relay";
|
||||
|
||||
export function isHex(str?: string) {
|
||||
if (str?.match(/^[0-9a-f]+$/i)) return true;
|
||||
@ -15,7 +16,10 @@ export function isHexKey(key?: string) {
|
||||
|
||||
export function safeDecode(str: string) {
|
||||
try {
|
||||
return nip19.decode(str);
|
||||
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);
|
||||
return result;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { kinds, validateEvent } from "nostr-tools";
|
||||
|
||||
import { ATag, DraftNostrEvent, ETag, isATag, isDTag, isETag, NostrEvent, RTag, Tag } from "../../types/nostr-event";
|
||||
import { RelayConfig, RelayMode } from "../../classes/relay";
|
||||
import { ATag, DraftNostrEvent, ETag, isATag, isDTag, isETag, NostrEvent, Tag } from "../../types/nostr-event";
|
||||
import { getMatchNostrLink } from "../regexp";
|
||||
import { AddressPointer, EventPointer } from "nostr-tools/lib/types/nip19";
|
||||
import { safeJson } from "../parse";
|
||||
import { safeDecode } from "../nip19";
|
||||
import { getEventUID } from "nostr-idb";
|
||||
import { safeRelayUrls } from "../relay";
|
||||
|
||||
export function truncatedId(str: string, keep = 6) {
|
||||
if (str.length < keep * 2 + 3) return str;
|
||||
@ -33,20 +33,34 @@ export function pointerMatchEvent(event: NostrEvent, pointer: AddressPointer | E
|
||||
return false;
|
||||
}
|
||||
|
||||
const isReplySymbol = Symbol("isReply");
|
||||
export function isReply(event: NostrEvent | DraftNostrEvent) {
|
||||
// @ts-ignore
|
||||
if (event[isReplySymbol] !== undefined) return event[isReplySymbol] as boolean;
|
||||
|
||||
if (event.kind === kinds.Repost || event.kind === kinds.GenericRepost) return false;
|
||||
// TODO: update this to only look for a "root" or "reply" tag
|
||||
return !!getThreadReferences(event).reply;
|
||||
const isReply = !!getThreadReferences(event).reply;
|
||||
// @ts-ignore
|
||||
event[isReplySymbol] = isReply;
|
||||
return isReply;
|
||||
}
|
||||
export function isMentionedInContent(event: NostrEvent | DraftNostrEvent, pubkey: string) {
|
||||
return filterTagsByContentRefs(event.content, event.tags).some((t) => t[1] === pubkey);
|
||||
}
|
||||
|
||||
const isRepostSymbol = Symbol("isRepost");
|
||||
export function isRepost(event: NostrEvent | DraftNostrEvent) {
|
||||
// @ts-ignore
|
||||
if (event[isRepostSymbol] !== undefined) return event[isRepostSymbol] as boolean;
|
||||
|
||||
if (event.kind === kinds.Repost || event.kind === kinds.GenericRepost) return true;
|
||||
|
||||
const match = event.content.match(getMatchNostrLink());
|
||||
return match && match[0].length === event.content.length;
|
||||
const isRepost = !!match && match[0].length === event.content.length;
|
||||
|
||||
// @ts-ignore
|
||||
event[isRepostSymbol] = isRepost;
|
||||
return isRepost;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,17 +186,6 @@ export function getThreadReferences(event: NostrEvent | DraftNostrEvent) {
|
||||
};
|
||||
}
|
||||
|
||||
export function parseRTag(tag: RTag): RelayConfig {
|
||||
switch (tag[2]) {
|
||||
case "write":
|
||||
return { url: tag[1], mode: RelayMode.WRITE };
|
||||
case "read":
|
||||
return { url: tag[1], mode: RelayMode.READ };
|
||||
default:
|
||||
return { url: tag[1], mode: RelayMode.ALL };
|
||||
}
|
||||
}
|
||||
|
||||
export function getEventCoordinate(event: NostrEvent) {
|
||||
const d = event.tags.find(isDTag)?.[1];
|
||||
return d ? `${event.kind}:${event.pubkey}:${d}` : `${event.kind}:${event.pubkey}`;
|
||||
@ -197,11 +200,11 @@ export function getEventAddressPointer(event: NostrEvent): AddressPointer {
|
||||
}
|
||||
|
||||
export function eTagToEventPointer(tag: ETag): EventPointer {
|
||||
return { id: tag[1], relays: tag[2] ? [tag[2]] : [] };
|
||||
return { id: tag[1], relays: tag[2] ? safeRelayUrls([tag[2]]) : [] };
|
||||
}
|
||||
export function aTagToAddressPointer(tag: ATag): AddressPointer {
|
||||
const cord = parseCoordinate(tag[1], true, false);
|
||||
if (tag[2]) cord.relays = [tag[2]];
|
||||
if (tag[2]) cord.relays = safeRelayUrls([tag[2]]);
|
||||
return cord;
|
||||
}
|
||||
export function addressPointerToATag(pointer: AddressPointer): ATag {
|
||||
|
@ -26,7 +26,7 @@ export function mapQueryMap(queryMap: RelayQueryMap, fn: (filter: NostrRequestFi
|
||||
return newMap;
|
||||
}
|
||||
|
||||
export function createSimpleQueryMap(relays: string[], filter: NostrRequestFilter) {
|
||||
export function createSimpleQueryMap(relays: Iterable<string>, filter: NostrRequestFilter) {
|
||||
const map: RelayQueryMap = {};
|
||||
for (const relay of relays) map[relay] = filter;
|
||||
return map;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { SimpleRelay, SubscriptionOptions } from "nostr-idb";
|
||||
import { Filter } from "nostr-tools";
|
||||
|
||||
import { RelayConfig } from "../classes/relay";
|
||||
import { NostrQuery, NostrRequestFilter } from "../types/nostr-query";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
|
||||
@ -55,18 +54,6 @@ export function safeRelayUrls(urls: string[]): string[] {
|
||||
return urls.map(safeRelayUrl).filter(Boolean) as string[];
|
||||
}
|
||||
|
||||
export function normalizeRelayConfigs(relays: RelayConfig[]) {
|
||||
const seen: string[] = [];
|
||||
return relays.reduce((newArr, r) => {
|
||||
const safeUrl = safeRelayUrl(r.url);
|
||||
if (safeUrl && !seen.includes(safeUrl)) {
|
||||
seen.push(safeUrl);
|
||||
newArr.push({ ...r, url: safeUrl });
|
||||
}
|
||||
return newArr;
|
||||
}, [] as RelayConfig[]);
|
||||
}
|
||||
|
||||
export function splitNostrFilterByPubkeys(
|
||||
filter: NostrRequestFilter,
|
||||
relayPubkeyMap: Record<string, string[]>,
|
||||
|
@ -4,20 +4,40 @@ import { useToast } from "@chakra-ui/react";
|
||||
import appSettings, { replaceSettings } from "../services/settings/app-settings";
|
||||
import useSubject from "./use-subject";
|
||||
import { AppSettings } from "../services/settings/migrations";
|
||||
import useCurrentAccount from "./use-current-account";
|
||||
import accountService from "../services/account";
|
||||
import userAppSettings from "../services/settings/user-app-settings";
|
||||
import { useSigningContext } from "../providers/global/signing-provider";
|
||||
import NostrPublishAction from "../classes/nostr-publish-action";
|
||||
import clientRelaysService from "../services/client-relays";
|
||||
|
||||
export default function useAppSettings() {
|
||||
const account = useCurrentAccount();
|
||||
const settings = useSubject(appSettings);
|
||||
const { requestSignature } = useSigningContext();
|
||||
const toast = useToast();
|
||||
|
||||
const updateSettings = useCallback(
|
||||
(newSettings: Partial<AppSettings>) => {
|
||||
async (newSettings: Partial<AppSettings>) => {
|
||||
try {
|
||||
if (!account) return;
|
||||
const full: AppSettings = { ...settings, ...newSettings };
|
||||
|
||||
if (account.readonly) {
|
||||
accountService.updateAccountLocalSettings(account.pubkey, full);
|
||||
appSettings.next(full);
|
||||
} else {
|
||||
const draft = userAppSettings.buildAppSettingsEvent(full);
|
||||
const signed = await requestSignature(draft);
|
||||
userAppSettings.receiveEvent(signed);
|
||||
new NostrPublishAction("Update Settings", clientRelaysService.outbox.urls, signed);
|
||||
}
|
||||
return replaceSettings({ ...settings, ...newSettings });
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||
}
|
||||
},
|
||||
[settings],
|
||||
[settings, account, requestSignature],
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -8,14 +8,14 @@ import useSingleEvent from "./use-single-event";
|
||||
|
||||
export default function useChannelMetadata(
|
||||
channelId: string | undefined,
|
||||
relays: string[] = [],
|
||||
relays: Iterable<string> = [],
|
||||
opts: RequestOptions = {},
|
||||
) {
|
||||
const channel = useSingleEvent(channelId);
|
||||
const sub = useMemo(() => {
|
||||
if (!channelId) return;
|
||||
return channelMetadataService.requestMetadata(relays, channelId, opts);
|
||||
}, [channelId, relays.join("|"), opts?.alwaysRequest, opts?.ignoreCache]);
|
||||
}, [channelId, Array.from(relays).join("|"), opts?.alwaysRequest, opts?.ignoreCache]);
|
||||
|
||||
const event = useSubject(sub);
|
||||
const baseMetadata = useMemo(() => channel && safeParseChannelMetadata(channel), [channel]);
|
||||
|
@ -1,28 +1,13 @@
|
||||
import { unique } from "../helpers/array";
|
||||
import clientRelaysService from "../services/client-relays";
|
||||
import { RelayMode } from "../classes/relay";
|
||||
import useSubject from "./use-subject";
|
||||
|
||||
export function useClientRelays(mode: RelayMode = RelayMode.ALL) {
|
||||
const relays = useSubject(clientRelaysService.relays) ?? [];
|
||||
|
||||
return relays.filter((r) => r.mode & mode);
|
||||
export function useReadRelayUrls(additional?: Iterable<string>) {
|
||||
const set = useSubject(clientRelaysService.readRelays);
|
||||
if (additional) return set.clone().merge(additional);
|
||||
return set;
|
||||
}
|
||||
export function useReadRelayUrls(additional: string[] = []) {
|
||||
const relays = useClientRelays(RelayMode.READ);
|
||||
const urls = relays.map((r) => r.url);
|
||||
if (additional.length > 0) {
|
||||
return unique([...urls, ...additional]);
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
export function useWriteRelayUrls(additional: string[] = []) {
|
||||
const relays = useClientRelays(RelayMode.WRITE);
|
||||
|
||||
const urls = relays.map((r) => r.url);
|
||||
if (additional) {
|
||||
return unique([...urls, ...additional]);
|
||||
}
|
||||
return urls;
|
||||
export function useWriteRelayUrls(additional?: Iterable<string>) {
|
||||
const set = useSubject(clientRelaysService.writeRelays);
|
||||
if (additional) return set.clone().merge(additional);
|
||||
return set;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export default function useEventBookmarkActions(event: NostrEvent) {
|
||||
: eventPointers.some((p) => p.id === event.id);
|
||||
|
||||
const removeBookmark = useCallback(async () => {
|
||||
const writeRelays = clientRelaysService.getWriteUrls();
|
||||
const writeRelays = clientRelaysService.outbox.urls;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
@ -53,7 +53,7 @@ export default function useEventBookmarkActions(event: NostrEvent) {
|
||||
}, [event, requestSignature, bookmarkList, isBookmarked]);
|
||||
|
||||
const addBookmark = useCallback(async () => {
|
||||
const writeRelays = clientRelaysService.getWriteUrls();
|
||||
const writeRelays = clientRelaysService.outbox.urls;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
|
@ -3,12 +3,12 @@ import eventReactionsService from "../services/event-reactions";
|
||||
import { useReadRelayUrls } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
|
||||
export default function useEventReactions(eventId: string, additionalRelays: string[] = [], alwaysRequest = true) {
|
||||
export default function useEventReactions(eventId: string, additionalRelays?: Iterable<string>, alwaysRequest = true) {
|
||||
const relays = useReadRelayUrls(additionalRelays);
|
||||
|
||||
const subject = useMemo(
|
||||
() => eventReactionsService.requestReactions(eventId, relays, alwaysRequest),
|
||||
[eventId, relays.join("|"), alwaysRequest],
|
||||
[eventId, relays.urls.join("|"), alwaysRequest],
|
||||
);
|
||||
|
||||
return useSubject(subject);
|
||||
|
@ -5,12 +5,12 @@ import { useReadRelayUrls } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
import { parseZapEvent } from "../helpers/nostr/zaps";
|
||||
|
||||
export default function useEventZaps(eventUID: string, additionalRelays: string[] = [], alwaysRequest = true) {
|
||||
const relays = useReadRelayUrls(additionalRelays);
|
||||
export default function useEventZaps(eventUID: string, additionalRelays?: Iterable<string>, alwaysRequest = true) {
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
|
||||
const subject = useMemo(
|
||||
() => eventZapsService.requestZaps(eventUID, relays, alwaysRequest),
|
||||
[eventUID, relays.join("|"), alwaysRequest],
|
||||
() => eventZapsService.requestZaps(eventUID, readRelays, alwaysRequest),
|
||||
[eventUID, readRelays.urls.join("|"), alwaysRequest],
|
||||
);
|
||||
|
||||
const events = useSubject(subject) || [];
|
||||
|
@ -4,17 +4,21 @@ import { useReadRelayUrls } from "./use-client-relays";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import Subject from "../classes/subject";
|
||||
|
||||
export default function useEventsReactions(eventIds: string[], additionalRelays: string[] = [], alwaysRequest = true) {
|
||||
const relays = useReadRelayUrls(additionalRelays);
|
||||
export default function useEventsReactions(
|
||||
eventIds: string[],
|
||||
additionalRelays?: Iterable<string>,
|
||||
alwaysRequest = true,
|
||||
) {
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
|
||||
// get subjects
|
||||
const subjects = useMemo(() => {
|
||||
const dir: Record<string, Subject<NostrEvent[]>> = {};
|
||||
for (const eventId of eventIds) {
|
||||
dir[eventId] = eventReactionsService.requestReactions(eventId, relays, alwaysRequest);
|
||||
dir[eventId] = eventReactionsService.requestReactions(eventId, readRelays, alwaysRequest);
|
||||
}
|
||||
return dir;
|
||||
}, [eventIds, relays.join("|"), alwaysRequest]);
|
||||
}, [eventIds, readRelays.urls.join("|"), alwaysRequest]);
|
||||
|
||||
// get values out of subjects
|
||||
const reactions: Record<string, NostrEvent[]> = {};
|
||||
|
@ -7,7 +7,7 @@ export const FAVORITE_LISTS_IDENTIFIER = "nostrudel-favorite-lists";
|
||||
|
||||
export default function useFavoriteEmojiPacks(
|
||||
pubkey?: string,
|
||||
additionalRelays: string[] = [],
|
||||
additionalRelays?: Iterable<string>,
|
||||
opts: RequestOptions = {},
|
||||
) {
|
||||
const account = useCurrentAccount();
|
||||
|
@ -7,7 +7,7 @@ import useSubject from "./use-subject";
|
||||
|
||||
export default function useReplaceableEvent(
|
||||
cord: string | CustomAddressPointer | undefined,
|
||||
additionalRelays: string[] = [],
|
||||
additionalRelays?: Iterable<string>,
|
||||
opts: RequestOptions = {},
|
||||
) {
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
@ -21,7 +21,7 @@ export default function useReplaceableEvent(
|
||||
parsed.identifier,
|
||||
opts,
|
||||
);
|
||||
}, [cord, readRelays.join("|"), opts?.alwaysRequest, opts?.ignoreCache]);
|
||||
}, [cord, readRelays.urls.join("|"), opts?.alwaysRequest, opts?.ignoreCache]);
|
||||
|
||||
return useSubject(sub);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import useSubjects from "./use-subjects";
|
||||
|
||||
export default function useReplaceableEvents(
|
||||
coordinates: string[] | CustomAddressPointer[] | undefined,
|
||||
additionalRelays: string[] = [],
|
||||
additionalRelays?: Iterable<string>,
|
||||
opts: RequestOptions = {},
|
||||
) {
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
@ -30,7 +30,7 @@ export default function useReplaceableEvents(
|
||||
);
|
||||
}
|
||||
return subs;
|
||||
}, [coordinates, readRelays.join("|")]);
|
||||
}, [coordinates, readRelays.urls.join("|")]);
|
||||
|
||||
return useSubjects(subs);
|
||||
}
|
||||
|
@ -1,18 +1,16 @@
|
||||
import { useMemo } from "react";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
import { RelayMode } from "../classes/relay";
|
||||
import relayScoreboardService from "../services/relay-scoreboard";
|
||||
import { useUserRelays } from "./use-user-relays";
|
||||
import useUserMailboxes from "./use-user-mailboxes";
|
||||
|
||||
/** @deprecated */
|
||||
export function useSharableProfileId(pubkey: string, relayCount = 2) {
|
||||
const userRelays = useUserRelays(pubkey);
|
||||
const mailboxes = useUserMailboxes(pubkey);
|
||||
|
||||
return useMemo(() => {
|
||||
const writeUrls = userRelays.filter((r) => r.mode & RelayMode.WRITE).map((r) => r.url);
|
||||
const ranked = relayScoreboardService.getRankedRelays(writeUrls);
|
||||
const ranked = relayScoreboardService.getRankedRelays(mailboxes?.outbox.urls);
|
||||
const onlyTwo = ranked.slice(0, relayCount);
|
||||
|
||||
return onlyTwo.length > 0 ? nip19.nprofileEncode({ pubkey, relays: onlyTwo }) : nip19.npubEncode(pubkey);
|
||||
}, [userRelays]);
|
||||
}, [mailboxes]);
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ import { useReadRelayUrls } from "./use-client-relays";
|
||||
import { useMemo } from "react";
|
||||
import useSubject from "./use-subject";
|
||||
|
||||
export default function useSingleEvent(id?: string, additionalRelays: string[] = []) {
|
||||
export default function useSingleEvent(id?: string, additionalRelays?: Iterable<string>) {
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
const subject = useMemo(() => {
|
||||
if (id) return singleEventService.requestEvent(id, readRelays);
|
||||
}, [id, readRelays.join("|")]);
|
||||
}, [id, readRelays.urls.join("|")]);
|
||||
|
||||
return useSubject(subject);
|
||||
}
|
||||
|
@ -4,11 +4,11 @@ import singleEventService from "../services/single-event";
|
||||
import { useReadRelayUrls } from "./use-client-relays";
|
||||
import useSubjects from "./use-subjects";
|
||||
|
||||
export default function useSingleEvents(ids?: string[], additionalRelays: string[] = []) {
|
||||
export default function useSingleEvents(ids?: string[], additionalRelays?: Iterable<string>) {
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
const subjects = useMemo(() => {
|
||||
return ids?.map((id) => singleEventService.requestEvent(id, readRelays)) ?? [];
|
||||
}, [ids, readRelays.join("|")]);
|
||||
}, [ids, readRelays.urls.join("|")]);
|
||||
|
||||
return useSubjects(subjects);
|
||||
}
|
||||
|
@ -9,19 +9,19 @@ import useSingleEvent from "./use-single-event";
|
||||
|
||||
export default function useStreamGoal(stream: ParsedStream) {
|
||||
const [goal, setGoal] = useState<NostrEvent>();
|
||||
const relays = useReadRelayUrls(stream.relays);
|
||||
const readRelays = useReadRelayUrls(stream.relays);
|
||||
|
||||
const streamGoal = useSingleEvent(stream.goal);
|
||||
|
||||
useEffect(() => {
|
||||
if (!stream.goal) {
|
||||
const request = new NostrRequest(relays);
|
||||
const request = new NostrRequest(readRelays);
|
||||
request.onEvent.subscribe((event) => {
|
||||
setGoal(event);
|
||||
});
|
||||
request.start({ "#a": [getATag(stream)], kinds: [GOAL_KIND] });
|
||||
}
|
||||
}, [stream.identifier, stream.goal, relays.join("|")]);
|
||||
}, [stream.identifier, stream.goal, readRelays.urls.join("|")]);
|
||||
|
||||
return streamGoal || goal;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import { unique } from "../helpers/array";
|
||||
|
||||
export default function useThreadTimelineLoader(
|
||||
focusedEvent: NostrEvent | undefined,
|
||||
relays: string[],
|
||||
relays: Iterable<string>,
|
||||
kind: number = kinds.ShortTextNote,
|
||||
) {
|
||||
const refs = focusedEvent && getThreadReferences(focusedEvent);
|
||||
|
@ -17,7 +17,7 @@ type Options = {
|
||||
|
||||
export default function useTimelineLoader(
|
||||
key: string,
|
||||
relays: string[],
|
||||
relays: Iterable<string>,
|
||||
query: NostrRequestFilter | undefined,
|
||||
opts?: Options,
|
||||
) {
|
||||
@ -28,7 +28,7 @@ export default function useTimelineLoader(
|
||||
timeline.setQueryMap(createSimpleQueryMap(relays, query));
|
||||
timeline.open();
|
||||
} else timeline.close();
|
||||
}, [timeline, JSON.stringify(query), relays.join("|")]);
|
||||
}, [timeline, JSON.stringify(query), Array.from(relays).join("|")]);
|
||||
|
||||
useEffect(() => {
|
||||
timeline.setEventFilter(opts?.eventFilter);
|
||||
|
@ -4,7 +4,7 @@ import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import useCurrentAccount from "./use-current-account";
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
|
||||
export default function useUserCommunitiesList(pubkey?: string, relays: string[] = [], opts?: RequestOptions) {
|
||||
export default function useUserCommunitiesList(pubkey?: string, relays?: Iterable<string>, opts?: RequestOptions) {
|
||||
const account = useCurrentAccount();
|
||||
const key = pubkey ?? account?.pubkey;
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
|
||||
export default function useUserContactList(
|
||||
pubkey?: string,
|
||||
additionalRelays: string[] = [],
|
||||
additionalRelays?: Iterable<string>,
|
||||
opts: RequestOptions = {},
|
||||
) {
|
||||
return useReplaceableEvent(pubkey && { kind: kinds.Contacts, pubkey }, additionalRelays, opts);
|
||||
|
@ -6,7 +6,7 @@ import useSubject from "./use-subject";
|
||||
import useTimelineLoader from "./use-timeline-loader";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
|
||||
export default function useUserLists(pubkey?: string, additionalRelays: string[] = []) {
|
||||
export default function useUserLists(pubkey?: string, additionalRelays?: Iterable<string>) {
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
const eventFilter = useCallback((event: NostrEvent) => {
|
||||
return !isJunkList(event);
|
||||
|
18
src/hooks/use-user-mailboxes.ts
Normal file
18
src/hooks/use-user-mailboxes.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import RelaySet from "../classes/relay-set";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import userMailboxesService from "../services/user-mailboxes";
|
||||
import { useReadRelayUrls } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
|
||||
export default function useUserMailboxes(pubkey: string, opts?: RequestOptions) {
|
||||
const readRelays = useReadRelayUrls();
|
||||
const sub = userMailboxesService.requestMailboxes(pubkey, readRelays, opts);
|
||||
const value = useSubject(sub);
|
||||
return value;
|
||||
}
|
||||
export function useUserInbox(pubkey: string, opts?: RequestOptions) {
|
||||
return useUserMailboxes(pubkey, opts)?.inbox ?? new RelaySet();
|
||||
}
|
||||
export function useUserOutbox(pubkey: string, opts?: RequestOptions) {
|
||||
return useUserMailboxes(pubkey, opts)?.outbox ?? new RelaySet();
|
||||
}
|
@ -5,7 +5,7 @@ import useSubject from "./use-subject";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { COMMON_CONTACT_RELAY } from "../const";
|
||||
|
||||
export function useUserMetadata(pubkey: string, additionalRelays: string[] = [], opts: RequestOptions = {}) {
|
||||
export function useUserMetadata(pubkey: string, additionalRelays: Iterable<string> = [], opts: RequestOptions = {}) {
|
||||
const relays = useReadRelayUrls([...additionalRelays, COMMON_CONTACT_RELAY]);
|
||||
|
||||
const subject = useMemo(() => userMetadataService.requestMetadata(pubkey, relays, opts), [pubkey, relays]);
|
||||
|
@ -27,7 +27,7 @@ export default function useUserMuteActions(pubkey: string) {
|
||||
draft = pruneExpiredPubkeys(draft);
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Mute", clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction("Mute", clientRelaysService.outbox.urls, signed);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
}, [requestSignature, muteList]);
|
||||
const unmute = useAsyncErrorHandler(async () => {
|
||||
@ -35,7 +35,7 @@ export default function useUserMuteActions(pubkey: string) {
|
||||
draft = pruneExpiredPubkeys(draft);
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Unmute", clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction("Unmute", clientRelaysService.outbox.urls, signed);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
}, [requestSignature, muteList]);
|
||||
|
||||
|
@ -2,6 +2,10 @@ import useReplaceableEvent from "./use-replaceable-event";
|
||||
import { MUTE_LIST_KIND } from "../helpers/nostr/lists";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
|
||||
export default function useUserMuteList(pubkey?: string, additionalRelays: string[] = [], opts: RequestOptions = {}) {
|
||||
export default function useUserMuteList(
|
||||
pubkey?: string,
|
||||
additionalRelays?: Iterable<string>,
|
||||
opts: RequestOptions = {},
|
||||
) {
|
||||
return useReplaceableEvent(pubkey && { kind: MUTE_LIST_KIND, pubkey }, additionalRelays, opts);
|
||||
}
|
||||
|
@ -5,7 +5,11 @@ import { PEOPLE_LIST_KIND, getPubkeysFromList } from "../helpers/nostr/lists";
|
||||
import useUserMuteList from "./use-user-mute-list";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
|
||||
export default function useUserMuteLists(pubkey?: string, additionalRelays: string[] = [], opts: RequestOptions = {}) {
|
||||
export default function useUserMuteLists(
|
||||
pubkey?: string,
|
||||
additionalRelays?: Iterable<string>,
|
||||
opts: RequestOptions = {},
|
||||
) {
|
||||
const muteList = useUserMuteList(pubkey, additionalRelays, opts);
|
||||
const altMuteList = useReplaceableEvent(
|
||||
pubkey && { kind: PEOPLE_LIST_KIND, pubkey, identifier: "mute" },
|
||||
|
@ -9,7 +9,7 @@ import useSubjects from "./use-subjects";
|
||||
import userMetadataService from "../services/user-metadata";
|
||||
import { Kind0ParsedContent } from "../helpers/user-metadata";
|
||||
|
||||
export function useUsersMetadata(pubkeys: string[], additionalRelays: string[] = []) {
|
||||
export function useUsersMetadata(pubkeys: string[], additionalRelays?: Iterable<string>) {
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
const metadataSubjects = useMemo(() => {
|
||||
return pubkeys.map((pubkey) => userMetadataService.requestMetadata(pubkey, readRelays));
|
||||
@ -27,7 +27,7 @@ export function useUsersMetadata(pubkeys: string[], additionalRelays: string[] =
|
||||
return metadataDir;
|
||||
}
|
||||
|
||||
export default function useUserNetwork(pubkey: string, additionalRelays: string[] = []) {
|
||||
export default function useUserNetwork(pubkey: string, additionalRelays?: Iterable<string>) {
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
const contacts = useUserContactList(pubkey);
|
||||
const contactsPubkeys = contacts ? getPubkeysFromList(contacts) : [];
|
||||
@ -36,7 +36,7 @@ export default function useUserNetwork(pubkey: string, additionalRelays: string[
|
||||
return contactsPubkeys.map((person) =>
|
||||
replaceableEventLoaderService.requestEvent(readRelays, kinds.Contacts, person.pubkey),
|
||||
);
|
||||
}, [contactsPubkeys, readRelays.join("|")]);
|
||||
}, [contactsPubkeys, readRelays.urls.join("|")]);
|
||||
|
||||
const lists = useSubjects(subjects);
|
||||
const metadata = useUsersMetadata(lists.map((list) => list.pubkey).concat(pubkey));
|
||||
@ -44,7 +44,7 @@ export default function useUserNetwork(pubkey: string, additionalRelays: string[
|
||||
return { lists, contacts, metadata };
|
||||
}
|
||||
|
||||
export function useNetworkConnectionCount(pubkey: string, additionalRelays: string[] = []) {
|
||||
export function useNetworkConnectionCount(pubkey: string, additionalRelays?: Iterable<string>) {
|
||||
const { lists, contacts } = useUserNetwork(pubkey, additionalRelays);
|
||||
const contactsPubkeys = contacts ? getPubkeysFromList(contacts) : [];
|
||||
|
||||
|
@ -7,7 +7,7 @@ import useSingleEvents from "./use-single-events";
|
||||
import { getEventCoordinate } from "../helpers/nostr/events";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
|
||||
export default function useUserProfileBadges(pubkey: string, additionalRelays: string[] = []) {
|
||||
export default function useUserProfileBadges(pubkey: string, additionalRelays?: Iterable<string>) {
|
||||
const profileBadgesEvent = useReplaceableEvent(
|
||||
{
|
||||
pubkey,
|
||||
|
@ -1,17 +1,20 @@
|
||||
import { useMemo } from "react";
|
||||
import userRelaysService from "../services/user-relays";
|
||||
|
||||
import userMailboxesService from "../services/user-mailboxes";
|
||||
import useSubject from "./use-subject";
|
||||
import { useReadRelayUrls } from "./use-client-relays";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { COMMON_CONTACT_RELAY } from "../const";
|
||||
import RelaySet from "../classes/relay-set";
|
||||
|
||||
export function useUserRelays(pubkey: string, additionalRelays: string[] = [], opts: RequestOptions = {}) {
|
||||
/** @deprecated */
|
||||
export function useUserRelays(pubkey: string, additionalRelays: Iterable<string> = [], opts: RequestOptions = {}) {
|
||||
const readRelays = useReadRelayUrls([...additionalRelays, COMMON_CONTACT_RELAY]);
|
||||
const subject = useMemo(
|
||||
() => userRelaysService.requestRelays(pubkey, readRelays, opts),
|
||||
[pubkey, readRelays.join("|")],
|
||||
() => userMailboxesService.requestMailboxes(pubkey, readRelays, opts),
|
||||
[pubkey, readRelays.urls.join("|")],
|
||||
);
|
||||
const userRelays = useSubject(subject);
|
||||
|
||||
return userRelays?.relays ?? [];
|
||||
return userRelays?.relays || new RelaySet();
|
||||
}
|
||||
|
@ -22,8 +22,8 @@ export function useRelaySelectionRelays() {
|
||||
}
|
||||
|
||||
export type RelaySelectionProviderProps = PropsWithChildren & {
|
||||
overrideDefault?: string[];
|
||||
additionalDefaults?: string[];
|
||||
overrideDefault?: Iterable<string>;
|
||||
additionalDefaults?: Iterable<string>;
|
||||
};
|
||||
|
||||
export default function RelaySelectionProvider({
|
||||
@ -34,13 +34,13 @@ export default function RelaySelectionProvider({
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const userReadRelays = useReadRelayUrls();
|
||||
const readRelays = useReadRelayUrls();
|
||||
const relays = useMemo(() => {
|
||||
if (location.state?.relays) return location.state.relays;
|
||||
if (overrideDefault) return overrideDefault;
|
||||
if (additionalDefaults) return unique([...userReadRelays, ...additionalDefaults]);
|
||||
return userReadRelays;
|
||||
}, [location.state?.relays, overrideDefault, userReadRelays.join("|"), additionalDefaults]);
|
||||
if (location.state?.relays) return location.state.relays as string[];
|
||||
if (overrideDefault) return Array.from(overrideDefault);
|
||||
if (additionalDefaults) return unique([...readRelays, ...additionalDefaults]);
|
||||
return readRelays.urls;
|
||||
}, [location.state?.relays, overrideDefault, readRelays.urls.join("|"), additionalDefaults]);
|
||||
|
||||
const setSelected = useCallback(
|
||||
(relays: string[]) => {
|
||||
|
@ -27,7 +27,6 @@ import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import signingService from "../../services/signing";
|
||||
import createDefer, { Deferred } from "../../classes/deferred";
|
||||
import useEventRelays from "../../hooks/use-event-relays";
|
||||
import { useWriteRelayUrls } from "../../hooks/use-client-relays";
|
||||
import { RelayFavicon } from "../../components/relay-favicon";
|
||||
import { ExternalLinkIcon } from "../../components/icons";
|
||||
import { getEventCoordinate, getEventUID, isReplaceable } from "../../helpers/nostr/events";
|
||||
@ -35,6 +34,7 @@ import NostrPublishAction from "../../classes/nostr-publish-action";
|
||||
import { Tag } from "../../types/nostr-event";
|
||||
import deleteEventService from "../../services/delete-events";
|
||||
import { EmbedEvent } from "../../components/embed-event";
|
||||
import { useWriteRelayUrls } from "../../hooks/use-client-relays";
|
||||
|
||||
type DeleteEventContextType = {
|
||||
isLoading: boolean;
|
||||
@ -86,7 +86,7 @@ export default function DeleteEventProvider({ children }: PropsWithChildren) {
|
||||
created_at: dayjs().unix(),
|
||||
};
|
||||
const signed = await signingService.requestSignature(draft, account);
|
||||
const pub = new NostrPublishAction("Delete", writeRelays, signed);
|
||||
new NostrPublishAction("Delete", writeRelays, signed);
|
||||
deleteEventService.handleEvent(signed);
|
||||
defer?.resolve();
|
||||
} catch (e) {
|
||||
@ -137,7 +137,7 @@ export default function DeleteEventProvider({ children }: PropsWithChildren) {
|
||||
</AccordionButton>
|
||||
<AccordionPanel>
|
||||
<Flex wrap="wrap" gap="2" py="2">
|
||||
{writeRelays.map((url) => (
|
||||
{writeRelays.urls.map((url) => (
|
||||
<Box alignItems="center" key={url} px="2" borderRadius="lg" display="flex" borderWidth="1px">
|
||||
<RelayFavicon relay={url} size="2xs" mr="2" />
|
||||
<Text isTruncated>{url}</Text>
|
||||
|
@ -69,7 +69,7 @@ function MuteModal({ pubkey, onClose, ...props }: Omit<ModalProps, "children"> &
|
||||
draft = muteListAddPubkey(draft, pubkey, expiration);
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Mute", clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction("Mute", clientRelaysService.outbox.urls, signed);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
onClose();
|
||||
} catch (e) {
|
||||
@ -139,7 +139,7 @@ function UnmuteHandler() {
|
||||
draft = pruneExpiredPubkeys(draft);
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Unmute", clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction("Unmute", clientRelaysService.outbox.urls, signed);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
return true;
|
||||
} catch (e) {
|
||||
@ -186,7 +186,7 @@ function UnmuteModal({ onClose }: Omit<ModalProps, "children">) {
|
||||
draft = pruneExpiredPubkeys(draft);
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Unmute", clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction("Unmute", clientRelaysService.outbox.urls, signed);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
onClose();
|
||||
} catch (e) {
|
||||
@ -204,7 +204,7 @@ function UnmuteModal({ onClose }: Omit<ModalProps, "children">) {
|
||||
}
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Extend mute", clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction("Extend mute", clientRelaysService.outbox.urls, signed);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
onClose();
|
||||
} catch (e) {
|
||||
@ -219,7 +219,7 @@ function UnmuteModal({ onClose }: Omit<ModalProps, "children">) {
|
||||
draft = muteListRemovePubkey(draft, pubkey);
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Unmute", clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction("Unmute", clientRelaysService.outbox.urls, signed);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||
@ -233,7 +233,7 @@ function UnmuteModal({ onClose }: Omit<ModalProps, "children">) {
|
||||
draft = muteListAddPubkey(draft, pubkey, expiration);
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Extend mute", clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction("Extend mute", clientRelaysService.outbox.urls, signed);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||
|
@ -228,7 +228,7 @@ class ChannelMetadataService {
|
||||
await transaction.commit();
|
||||
}
|
||||
|
||||
private requestChannelMetadataFromRelays(relays: string[], channelId: string) {
|
||||
private requestChannelMetadataFromRelays(relays: Iterable<string>, channelId: string) {
|
||||
const sub = this.metadata.get(channelId);
|
||||
|
||||
const relayUrls = Array.from(relays);
|
||||
@ -249,7 +249,7 @@ class ChannelMetadataService {
|
||||
return sub;
|
||||
}
|
||||
|
||||
requestMetadata(relays: string[], channelId: string, opts: RequestOptions = {}) {
|
||||
requestMetadata(relays: Iterable<string>, channelId: string, opts: RequestOptions = {}) {
|
||||
const sub = this.metadata.get(channelId);
|
||||
|
||||
if (!sub.value) {
|
||||
|
@ -1,36 +1,24 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { unique } from "../helpers/array";
|
||||
import { DraftNostrEvent, RTag } from "../types/nostr-event";
|
||||
import accountService from "./account";
|
||||
import { RelayConfig, RelayMode } from "../classes/relay";
|
||||
import userRelaysService, { ParsedUserRelays } from "./user-relays";
|
||||
import { Connection, PersistentSubject, Subject } from "../classes/subject";
|
||||
import signingService from "./signing";
|
||||
import { RelayMode } from "../classes/relay";
|
||||
import userMailboxesService, { UserMailboxes } from "./user-mailboxes";
|
||||
import { PersistentSubject, Subject } from "../classes/subject";
|
||||
import { logger } from "../helpers/debug";
|
||||
import NostrPublishAction from "../classes/nostr-publish-action";
|
||||
import { COMMON_CONTACT_RELAY } from "../const";
|
||||
import appSettings from "./settings/app-settings";
|
||||
import RelaySet from "../classes/relay-set";
|
||||
import { safeUrl } from "../helpers/parse";
|
||||
|
||||
export type RelayDirectory = Record<string, { read: boolean; write: boolean }>;
|
||||
|
||||
const DEFAULT_RELAYS = [
|
||||
{ url: "wss://relay.damus.io", mode: RelayMode.READ },
|
||||
{ url: "wss://nostr.wine", mode: RelayMode.READ },
|
||||
{ url: "wss://relay.snort.social", mode: RelayMode.READ },
|
||||
{ url: "wss://nos.lol", mode: RelayMode.READ },
|
||||
{ url: "wss://purplerelay.com", mode: RelayMode.READ },
|
||||
];
|
||||
|
||||
const userRelaysToRelayConfig: Connection<ParsedUserRelays, RelayConfig[], RelayConfig[] | undefined> = (
|
||||
userRelays,
|
||||
next,
|
||||
) => next(userRelays.relays);
|
||||
// const userRelaysToRelayConfig: Connection<ParsedUserRelays, RelayConfig[], RelayConfig[] | undefined> = (
|
||||
// userRelays,
|
||||
// next,
|
||||
// ) => next(userRelays.relays);
|
||||
|
||||
class ClientRelayService {
|
||||
// bootstrapRelays = new Set<string>();
|
||||
relays = new PersistentSubject<RelayConfig[]>([]);
|
||||
writeRelays = new PersistentSubject<RelayConfig[]>([]);
|
||||
readRelays = new PersistentSubject<RelayConfig[]>([]);
|
||||
readRelays = new PersistentSubject(new RelaySet());
|
||||
writeRelays = new PersistentSubject(new RelaySet());
|
||||
// outbox = new PersistentSubject(new RelaySet());
|
||||
// inbox = new PersistentSubject(new RelaySet());
|
||||
|
||||
log = logger.extend("ClientRelays");
|
||||
|
||||
@ -38,127 +26,96 @@ class ClientRelayService {
|
||||
accountService.loading.subscribe(this.handleAccountChange, this);
|
||||
accountService.current.subscribe(this.handleAccountChange, this);
|
||||
|
||||
appSettings.subscribe(this.handleSettingsChange, this);
|
||||
|
||||
// set the read and write relays
|
||||
this.relays.subscribe((relays) => {
|
||||
this.log("Got new relay list", relays);
|
||||
this.writeRelays.next(relays.filter((r) => r.mode & RelayMode.WRITE));
|
||||
this.readRelays.next(relays.filter((r) => r.mode & RelayMode.READ));
|
||||
});
|
||||
// this.relays.subscribe((relays) => {
|
||||
// this.log("Got new relay list", relays);
|
||||
// this.outbox.next(relays.filter((r) => r.mode & RelayMode.WRITE));
|
||||
// this.inbox.next(relays.filter((r) => r.mode & RelayMode.READ));
|
||||
// });
|
||||
}
|
||||
|
||||
private userRequestRelaySubject: Subject<ParsedUserRelays> | undefined;
|
||||
addRelay(url: string, mode: RelayMode) {
|
||||
if (mode & RelayMode.WRITE) this.writeRelays.next(this.writeRelays.value.clone().add(url));
|
||||
if (mode & RelayMode.READ) this.readRelays.next(this.readRelays.value.clone().add(url));
|
||||
}
|
||||
removeRelay(url: string, mode: RelayMode) {
|
||||
if (mode & RelayMode.WRITE) {
|
||||
const next = this.writeRelays.value.clone();
|
||||
next.delete(url);
|
||||
this.writeRelays.next(next);
|
||||
}
|
||||
if (mode & RelayMode.READ) {
|
||||
const next = this.readRelays.value.clone();
|
||||
next.delete(url);
|
||||
this.readRelays.next(next);
|
||||
}
|
||||
}
|
||||
|
||||
private handleSettingsChange() {
|
||||
this.readRelays.next(RelaySet.from(appSettings.value.defaultRelays));
|
||||
this.writeRelays.next(new RelaySet());
|
||||
}
|
||||
|
||||
private userRelaySub: Subject<UserMailboxes> | undefined;
|
||||
private handleAccountChange() {
|
||||
// skip if account is loading
|
||||
if (accountService.loading.value) return;
|
||||
|
||||
// disconnect the relay list subject
|
||||
if (this.userRequestRelaySubject) {
|
||||
this.relays.disconnect(this.userRequestRelaySubject);
|
||||
this.userRequestRelaySubject = undefined;
|
||||
}
|
||||
// if (this.userRelaySub) {
|
||||
// this.relays.disconnect(this.userRelaySub);
|
||||
// this.userRelaySub.unsubscribe(this.handleUserRelays, this);
|
||||
// this.userRelaySub = undefined;
|
||||
// }
|
||||
|
||||
const account = accountService.current.value;
|
||||
if (!account) {
|
||||
this.log("No account, using default relays");
|
||||
this.relays.next(DEFAULT_RELAYS);
|
||||
return;
|
||||
}
|
||||
|
||||
// clear relays
|
||||
this.relays.next([]);
|
||||
if (!account) return;
|
||||
|
||||
// connect the relay subject with the account relay subject
|
||||
this.userRequestRelaySubject = userRelaysService.getRelays(account.pubkey);
|
||||
this.relays.connectWithHandler(this.userRequestRelaySubject, userRelaysToRelayConfig);
|
||||
// this.userRelaySub = userMailboxesService.requestMailboxes(account.pubkey, [COMMON_CONTACT_RELAY]);
|
||||
// this.userRelaySub.subscribe(this.handleUserRelays, this);
|
||||
// this.relays.connectWithHandler(this.userRelaySub, userRelaysToRelayConfig);
|
||||
|
||||
// load the relays from cache
|
||||
if (!userRelaysService.getRelays(account.pubkey).value) {
|
||||
this.log("Load users relay list from cache");
|
||||
userRelaysService.loadFromCache(account.pubkey).then(() => {
|
||||
if (this.relays.value.length === 0) {
|
||||
const bootstrapRelays = account.relays ?? [COMMON_CONTACT_RELAY];
|
||||
// if (!userRelaysService.getRelays(account.pubkey).value) {
|
||||
// this.log("Load users relay list from cache");
|
||||
// userRelaysService.loadFromCache(account.pubkey).then(() => {
|
||||
// if (this.relays.value.length === 0) {
|
||||
// const bootstrapRelays = account.relays ?? [COMMON_CONTACT_RELAY];
|
||||
|
||||
this.log("Loading relay list from bootstrap relays", bootstrapRelays);
|
||||
userRelaysService.requestRelays(account.pubkey, bootstrapRelays, { alwaysRequest: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
// this.log("Loading relay list from bootstrap relays", bootstrapRelays);
|
||||
// userRelaysService.requestRelays(account.pubkey, bootstrapRelays, { alwaysRequest: true });
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// double check for new relay notes
|
||||
setTimeout(() => {
|
||||
if (this.relays.value.length === 0) return;
|
||||
// setTimeout(() => {
|
||||
// if (this.relays.value.length === 0) return;
|
||||
|
||||
this.log("Requesting latest relay list from relays");
|
||||
userRelaysService.requestRelays(account.pubkey, this.getWriteUrls(), { alwaysRequest: true });
|
||||
}, 5000);
|
||||
// this.log("Requesting latest relay list from relays");
|
||||
// userRelaysService.requestRelays(account.pubkey, this.getOutboxURLs(), { alwaysRequest: true });
|
||||
// }, 5000);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
async addRelay(url: string, mode: RelayMode) {
|
||||
this.log(`Adding ${url} relay`);
|
||||
if (!this.relays.value.some((r) => r.url === url)) {
|
||||
const newRelays = [...this.relays.value, { url, mode }];
|
||||
await this.postUpdatedRelays(newRelays);
|
||||
}
|
||||
// private handleUserRelays(userRelays: UserMailboxes) {
|
||||
// if (userRelays.pubkey === accountService.current.value?.pubkey) {
|
||||
// this.inbox.next(userRelays.mailboxes.filter(RelayMode.READ));
|
||||
// this.outbox.next(userRelays.mailboxes.filter(RelayMode.WRITE));
|
||||
// }
|
||||
// }
|
||||
|
||||
get outbox() {
|
||||
const account = accountService.current.value;
|
||||
if (account) return userMailboxesService.getMailboxes(account.pubkey).value?.outbox ?? this.writeRelays.value;
|
||||
return this.writeRelays.value;
|
||||
}
|
||||
/** @deprecated */
|
||||
async updateRelay(url: string, mode: RelayMode) {
|
||||
this.log(`Updating ${url} relay`);
|
||||
if (this.relays.value.some((r) => r.url === url)) {
|
||||
const newRelays = this.relays.value.map((r) => (r.url === url ? { url, mode } : r));
|
||||
await this.postUpdatedRelays(newRelays);
|
||||
}
|
||||
}
|
||||
/** @deprecated */
|
||||
async removeRelay(url: string) {
|
||||
this.log(`Removing ${url} relay`);
|
||||
if (this.relays.value.some((r) => r.url === url)) {
|
||||
const newRelays = this.relays.value.filter((r) => r.url !== url);
|
||||
await this.postUpdatedRelays(newRelays);
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
async postUpdatedRelays(newRelays: RelayConfig[]) {
|
||||
const rTags: RTag[] = newRelays.map((r) => {
|
||||
switch (r.mode) {
|
||||
case RelayMode.READ:
|
||||
return ["r", r.url, "read"];
|
||||
case RelayMode.WRITE:
|
||||
return ["r", r.url, "write"];
|
||||
case RelayMode.ALL:
|
||||
default:
|
||||
return ["r", r.url];
|
||||
}
|
||||
});
|
||||
|
||||
const draft: DraftNostrEvent = {
|
||||
kind: 10002,
|
||||
tags: rTags,
|
||||
content: "",
|
||||
created_at: dayjs().unix(),
|
||||
};
|
||||
|
||||
const newRelayUrls = newRelays.filter((r) => r.mode & RelayMode.WRITE).map((r) => r.url);
|
||||
const oldRelayUrls = this.relays.value.filter((r) => r.mode & RelayMode.WRITE).map((r) => r.url);
|
||||
const writeUrls = unique([...oldRelayUrls, ...newRelayUrls, COMMON_CONTACT_RELAY]);
|
||||
|
||||
const current = accountService.current.value;
|
||||
if (!current) throw new Error("no account");
|
||||
const signed = await signingService.requestSignature(draft, current);
|
||||
|
||||
const pub = new NostrPublishAction("Update Relays", writeUrls, signed);
|
||||
|
||||
// pass new event to the user relay service
|
||||
userRelaysService.receiveEvent(signed);
|
||||
|
||||
await pub.onComplete;
|
||||
}
|
||||
|
||||
getWriteUrls() {
|
||||
return this.relays.value?.filter((r) => r.mode & RelayMode.WRITE).map((r) => r.url);
|
||||
}
|
||||
getReadUrls() {
|
||||
return this.relays.value?.filter((r) => r.mode & RelayMode.READ).map((r) => r.url);
|
||||
get inbox() {
|
||||
const account = accountService.current.value;
|
||||
if (account) return userMailboxesService.getMailboxes(account.pubkey).value?.inbox ?? this.readRelays.value;
|
||||
return this.readRelays.value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ class EventReactionsService {
|
||||
subjects = new SuperMap<eventId, Subject<NostrEvent[]>>(() => new Subject<NostrEvent[]>([]));
|
||||
pending = new SuperMap<eventId, Set<relay>>(() => new Set());
|
||||
|
||||
requestReactions(eventId: string, relays: relay[], alwaysRequest = true) {
|
||||
requestReactions(eventId: string, relays: Iterable<string>, alwaysRequest = true) {
|
||||
const subject = this.subjects.get(eventId);
|
||||
|
||||
if (!subject.value || alwaysRequest) {
|
||||
|
@ -14,7 +14,7 @@ class EventZapsService {
|
||||
subjects = new SuperMap<eventUID, Subject<NostrEvent[]>>(() => new Subject<NostrEvent[]>([]));
|
||||
pending = new SuperMap<eventUID, Set<relay>>(() => new Set());
|
||||
|
||||
requestZaps(eventUID: eventUID, relays: relay[], alwaysRequest = true) {
|
||||
requestZaps(eventUID: eventUID, relays: Iterable<string>, alwaysRequest = true) {
|
||||
const subject = this.subjects.get(eventUID);
|
||||
|
||||
if (!subject.value || alwaysRequest) {
|
||||
|
@ -201,8 +201,8 @@ class RelayScoreboardService {
|
||||
return score;
|
||||
}
|
||||
|
||||
getRankedRelays(customRelays?: string[]) {
|
||||
const relays = customRelays ?? this.getRelays();
|
||||
getRankedRelays(urls?: Iterable<string>) {
|
||||
const relays = (urls && Array.from(urls)) ?? this.getRelays();
|
||||
const relayScores = new Map<string, number>();
|
||||
|
||||
for (const relay of relays) {
|
||||
|
@ -243,7 +243,7 @@ class ReplaceableEventLoaderService {
|
||||
this.writeToCacheThrottle();
|
||||
}
|
||||
|
||||
private requestEventFromRelays(relays: string[], kind: number, pubkey: string, d?: string) {
|
||||
private requestEventFromRelays(relays: Iterable<string>, kind: number, pubkey: string, d?: string) {
|
||||
const cord = createCoordinate(kind, pubkey, d);
|
||||
const sub = this.events.get(cord);
|
||||
|
||||
@ -261,7 +261,7 @@ class ReplaceableEventLoaderService {
|
||||
return sub;
|
||||
}
|
||||
|
||||
requestEvent(relays: string[], kind: number, pubkey: string, d?: string, opts: RequestOptions = {}) {
|
||||
requestEvent(relays: Iterable<string>, kind: number, pubkey: string, d?: string, opts: RequestOptions = {}) {
|
||||
const cord = createCoordinate(kind, pubkey, d);
|
||||
const sub = this.events.get(cord);
|
||||
|
||||
|
@ -25,7 +25,7 @@ export async function replaceSettings(newSettings: AppSettings) {
|
||||
const draft = userAppSettings.buildAppSettingsEvent(newSettings);
|
||||
const signed = await signingService.requestSignature(draft, account);
|
||||
userAppSettings.receiveEvent(signed);
|
||||
new NostrPublishAction("Update Settings", clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction("Update Settings", clientRelaysService.outbox.urls, signed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,20 +44,20 @@ accountService.current.subscribe(() => {
|
||||
log("Loaded user settings from local storage");
|
||||
}
|
||||
|
||||
const subject = userAppSettings.requestAppSettings(account.pubkey, clientRelaysService.getReadUrls(), {
|
||||
const subject = userAppSettings.requestAppSettings(account.pubkey, clientRelaysService.inbox.urls, {
|
||||
alwaysRequest: true,
|
||||
});
|
||||
appSettings.next(defaultSettings);
|
||||
appSettings.connect(subject);
|
||||
});
|
||||
|
||||
clientRelaysService.relays.subscribe(() => {
|
||||
// relays changed, look for settings again
|
||||
const account = accountService.current.value;
|
||||
// clientRelaysService.relays.subscribe(() => {
|
||||
// // relays changed, look for settings again
|
||||
// const account = accountService.current.value;
|
||||
|
||||
if (account) {
|
||||
userAppSettings.requestAppSettings(account.pubkey, clientRelaysService.getReadUrls(), { alwaysRequest: true });
|
||||
}
|
||||
});
|
||||
// if (account) {
|
||||
// userAppSettings.requestAppSettings(account.pubkey, clientRelaysService.getInboxURLs(), { alwaysRequest: true });
|
||||
// }
|
||||
// });
|
||||
|
||||
export default appSettings;
|
||||
|
@ -5,6 +5,7 @@ import { safeJson } from "../../helpers/parse";
|
||||
export type AppSettingsV0 = {
|
||||
version: 0;
|
||||
colorMode: ColorModeWithSystem;
|
||||
defaultRelays: string[];
|
||||
blurImages: boolean;
|
||||
autoShowMedia: boolean;
|
||||
proxyUserMedia: boolean;
|
||||
@ -32,6 +33,7 @@ export type AppSettingsV3 = Omit<AppSettingsV2, "version"> & { version: 3; quick
|
||||
export type AppSettingsV4 = Omit<AppSettingsV3, "version"> & { version: 4; loadOpenGraphData: boolean };
|
||||
export type AppSettingsV5 = Omit<AppSettingsV4, "version"> & { version: 5; hideUsernames: boolean };
|
||||
export type AppSettingsV6 = Omit<AppSettingsV5, "version"> & { version: 6; noteDifficulty: number | null };
|
||||
export type AppSettingsV7 = Omit<AppSettingsV6, "version"> & { version: 7; defaultRelays: string[] };
|
||||
|
||||
export function isV0(settings: { version: number }): settings is AppSettingsV0 {
|
||||
return settings.version === undefined || settings.version === 0;
|
||||
@ -61,9 +63,9 @@ export const defaultSettings: AppSettings = {
|
||||
version: 6,
|
||||
theme: "default",
|
||||
colorMode: "system",
|
||||
defaultRelays: ["wss://relay.damus.io", "wss://nostr.wine", "wss://nos.lol", "wss://welcome.nostr.wine"],
|
||||
maxPageWidth: "none",
|
||||
blurImages: true,
|
||||
// nostr:nevent1qqsxvkjgpc6zhydj4rxjpl0frev7hmgynruq027mujdgy2hwjypaqfspzpmhxue69uhkummnw3ezuamfdejszythwden5te0dehhxarjw4jjucm0d5sfntd0
|
||||
hideUsernames: false,
|
||||
autoShowMedia: true,
|
||||
proxyUserMedia: false,
|
||||
|
@ -5,7 +5,7 @@ import Subject from "../classes/subject";
|
||||
import SuperMap from "../classes/super-map";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { localRelay } from "./local-relay";
|
||||
import { relayRequest, safeRelayUrls } from "../helpers/relay";
|
||||
import { relayRequest } from "../helpers/relay";
|
||||
import { logger } from "../helpers/debug";
|
||||
|
||||
const RELAY_REQUEST_BATCH_TIME = 500;
|
||||
@ -15,12 +15,11 @@ class SingleEventService {
|
||||
pending = new Map<string, string[]>();
|
||||
log = logger.extend("SingleEvent");
|
||||
|
||||
requestEvent(id: string, relays: string[]) {
|
||||
requestEvent(id: string, relays: Iterable<string>) {
|
||||
const subject = this.cache.get(id);
|
||||
if (subject.value) return subject;
|
||||
|
||||
relays = safeRelayUrls(relays);
|
||||
this.pending.set(id, this.pending.get(id)?.concat(relays) ?? relays);
|
||||
this.pending.set(id, this.pending.get(id)?.concat(Array.from(relays)) ?? Array.from(relays));
|
||||
this.batchRequestsThrottle();
|
||||
|
||||
return subject;
|
||||
|
@ -4,32 +4,35 @@ import { isPTag, NostrEvent } from "../types/nostr-event";
|
||||
import { safeJson } from "../helpers/parse";
|
||||
import SuperMap from "../classes/super-map";
|
||||
import Subject from "../classes/subject";
|
||||
import { RelayConfig, RelayMode } from "../classes/relay";
|
||||
import { normalizeRelayConfigs } from "../helpers/relay";
|
||||
import replaceableEventLoaderService, { RequestOptions } from "./replaceable-event-requester";
|
||||
import RelaySet from "../classes/relay-set";
|
||||
|
||||
export type UserContacts = {
|
||||
pubkey: string;
|
||||
relays: RelayConfig[];
|
||||
relays: RelaySet;
|
||||
inbox: RelaySet;
|
||||
outbox: RelaySet;
|
||||
contacts: string[];
|
||||
contactRelay: Record<string, string | undefined>;
|
||||
created_at: number;
|
||||
};
|
||||
|
||||
type RelayJson = Record<string, { read: boolean; write: boolean }>;
|
||||
function relayJsonToRelayConfig(relayJson: RelayJson): RelayConfig[] {
|
||||
try {
|
||||
return Array.from(Object.entries(relayJson)).map(([url, opts]) => ({
|
||||
url,
|
||||
mode: (opts.write ? RelayMode.WRITE : 0) | (opts.read ? RelayMode.READ : 0),
|
||||
}));
|
||||
} catch (e) {}
|
||||
return [];
|
||||
function relayJsonToMailboxes(relayJson: RelayJson) {
|
||||
const relays = new RelaySet();
|
||||
const inbox = new RelaySet();
|
||||
const outbox = new RelaySet();
|
||||
for (const [url, opts] of Object.entries(relayJson)) {
|
||||
relays.add(url);
|
||||
if (opts.write) outbox.add(url);
|
||||
if (opts.read) inbox.add(url);
|
||||
}
|
||||
return { relays, inbox, outbox };
|
||||
}
|
||||
|
||||
function parseContacts(event: NostrEvent): UserContacts {
|
||||
const relayJson = safeJson(event.content, {}) as RelayJson;
|
||||
const relays = normalizeRelayConfigs(relayJsonToRelayConfig(relayJson));
|
||||
const { relays, inbox, outbox } = relayJsonToMailboxes(relayJson);
|
||||
|
||||
const pubkeys = event.tags.filter(isPTag).map((tag) => tag[1]);
|
||||
const contactRelay = event.tags.filter(isPTag).reduce(
|
||||
@ -45,6 +48,8 @@ function parseContacts(event: NostrEvent): UserContacts {
|
||||
return {
|
||||
pubkey: event.pubkey,
|
||||
relays,
|
||||
inbox,
|
||||
outbox,
|
||||
contacts: pubkeys,
|
||||
contactRelay,
|
||||
created_at: event.created_at,
|
||||
@ -56,7 +61,7 @@ class UserContactsService {
|
||||
getSubject(pubkey: string) {
|
||||
return this.subjects.get(pubkey);
|
||||
}
|
||||
requestContacts(pubkey: string, relays: string[], opts?: RequestOptions) {
|
||||
requestContacts(pubkey: string, relays: Iterable<string>, opts?: RequestOptions) {
|
||||
const sub = this.subjects.get(pubkey);
|
||||
|
||||
const requestSub = replaceableEventLoaderService.requestEvent(relays, kinds.Contacts, pubkey, undefined, opts);
|
||||
|
@ -1,44 +1,53 @@
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { isRTag, NostrEvent } from "../types/nostr-event";
|
||||
import { RelayConfig } from "../classes/relay";
|
||||
import { parseRTag } from "../helpers/nostr/events";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import SuperMap from "../classes/super-map";
|
||||
import Subject from "../classes/subject";
|
||||
import { normalizeRelayConfigs } from "../helpers/relay";
|
||||
import userContactsService from "./user-contacts";
|
||||
import replaceableEventLoaderService, { createCoordinate, RequestOptions } from "./replaceable-event-requester";
|
||||
import RelaySet from "../classes/relay-set";
|
||||
import { RelayMode } from "../classes/relay";
|
||||
|
||||
export type ParsedUserRelays = {
|
||||
export type UserMailboxes = {
|
||||
pubkey: string;
|
||||
relays: RelayConfig[];
|
||||
relays: RelaySet;
|
||||
inbox: RelaySet;
|
||||
outbox: RelaySet;
|
||||
created_at: number;
|
||||
};
|
||||
|
||||
function parseRelaysEvent(event: NostrEvent): ParsedUserRelays {
|
||||
function nip65ToUserMailboxes(event: NostrEvent): UserMailboxes {
|
||||
return {
|
||||
pubkey: event.pubkey,
|
||||
relays: normalizeRelayConfigs(event.tags.filter(isRTag).map(parseRTag)),
|
||||
relays: RelaySet.fromNIP65Event(event),
|
||||
inbox: RelaySet.fromNIP65Event(event, RelayMode.READ),
|
||||
outbox: RelaySet.fromNIP65Event(event, RelayMode.WRITE),
|
||||
created_at: event.created_at,
|
||||
};
|
||||
}
|
||||
|
||||
class UserRelaysService {
|
||||
private subjects = new SuperMap<string, Subject<ParsedUserRelays>>(() => new Subject<ParsedUserRelays>());
|
||||
getRelays(pubkey: string) {
|
||||
class UserMailboxesService {
|
||||
private subjects = new SuperMap<string, Subject<UserMailboxes>>(() => new Subject<UserMailboxes>());
|
||||
getMailboxes(pubkey: string) {
|
||||
return this.subjects.get(pubkey);
|
||||
}
|
||||
requestRelays(pubkey: string, relays: string[], opts: RequestOptions = {}) {
|
||||
requestMailboxes(pubkey: string, relays: Iterable<string>, opts: RequestOptions = {}) {
|
||||
const sub = this.subjects.get(pubkey);
|
||||
const requestSub = replaceableEventLoaderService.requestEvent(relays, kinds.RelayList, pubkey, undefined, opts);
|
||||
sub.connectWithHandler(requestSub, (event, next) => next(parseRelaysEvent(event)));
|
||||
sub.connectWithHandler(requestSub, (event, next) => next(nip65ToUserMailboxes(event)));
|
||||
|
||||
// also fetch the relays from the users contacts
|
||||
const contactsSub = userContactsService.requestContacts(pubkey, relays, opts);
|
||||
sub.connectWithHandler(contactsSub, (contacts, next, value) => {
|
||||
// NOTE: only use relays from contact list if the user dose not have a NIP-65 relay list
|
||||
if (contacts.relays.length > 0 && !value) {
|
||||
next({ pubkey: contacts.pubkey, relays: contacts.relays, created_at: contacts.created_at });
|
||||
if (contacts.relays.size > 0 && !value) {
|
||||
next({
|
||||
pubkey: contacts.pubkey,
|
||||
relays: contacts.relays,
|
||||
inbox: contacts.inbox,
|
||||
outbox: contacts.outbox,
|
||||
created_at: contacts.created_at,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -52,7 +61,7 @@ class UserRelaysService {
|
||||
await replaceableEventLoaderService.loadFromCache(createCoordinate(kinds.RelayList, pubkey));
|
||||
|
||||
const requestSub = replaceableEventLoaderService.getEvent(kinds.RelayList, pubkey);
|
||||
sub.connectWithHandler(requestSub, (event, next) => next(parseRelaysEvent(event)));
|
||||
sub.connectWithHandler(requestSub, (event, next) => next(nip65ToUserMailboxes(event)));
|
||||
}
|
||||
|
||||
receiveEvent(event: NostrEvent) {
|
||||
@ -60,11 +69,11 @@ class UserRelaysService {
|
||||
}
|
||||
}
|
||||
|
||||
const userRelaysService = new UserRelaysService();
|
||||
const userMailboxesService = new UserMailboxesService();
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-ignore
|
||||
window.userRelaysService = userRelaysService;
|
||||
window.userMailboxesService = userMailboxesService;
|
||||
}
|
||||
|
||||
export default userRelaysService;
|
||||
export default userMailboxesService;
|
@ -24,7 +24,7 @@ class UserMetadataService {
|
||||
getSubject(pubkey: string) {
|
||||
return this.parsedSubjects.get(pubkey);
|
||||
}
|
||||
requestMetadata(pubkey: string, relays: string[], opts: RequestOptions = {}) {
|
||||
requestMetadata(pubkey: string, relays: Iterable<string>, opts: RequestOptions = {}) {
|
||||
const sub = this.parsedSubjects.get(pubkey);
|
||||
const requestSub = replaceableEventLoaderService.requestEvent(relays, kinds.Metadata, pubkey, undefined, opts);
|
||||
sub.connectWithHandler(requestSub, (event, next) => next(parseKind0Event(event)));
|
||||
|
@ -39,11 +39,7 @@ export default function ChannelJoinButton({
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
|
||||
new NostrPublishAction(
|
||||
isSubscribed ? "Leave Channel" : "Join Channel",
|
||||
clientRelaysService.getWriteUrls(),
|
||||
signed,
|
||||
);
|
||||
new NostrPublishAction(isSubscribed ? "Leave Channel" : "Join Channel", clientRelaysService.outbox.urls, signed);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ export default function ChannelMessageForm({
|
||||
|
||||
setLoadingMessage("Signing...");
|
||||
const signed = await requestSignature(draft);
|
||||
const writeRelays = clientRelaysService.getWriteUrls();
|
||||
const writeRelays = clientRelaysService.outbox.urls;
|
||||
new NostrPublishAction("Send DM", writeRelays, signed);
|
||||
reset();
|
||||
|
||||
|
@ -43,7 +43,7 @@ export default function CommunityJoinButton({
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
|
||||
new NostrPublishAction(isSubscribed ? "Unsubscribe" : "Subscribe", clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction(isSubscribed ? "Unsubscribe" : "Subscribe", clientRelaysService.outbox.urls, signed);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import { parseCoordinate } from "../../helpers/nostr/events";
|
||||
import UserAvatarLink from "../../components/user-avatar-link";
|
||||
import { AddressPointer } from "nostr-tools/lib/types/nip19";
|
||||
|
||||
export function useUsersJoinedCommunitiesLists(pubkeys: string[], additionalRelays: string[] = []) {
|
||||
export function useUsersJoinedCommunitiesLists(pubkeys: string[], additionalRelays?: Iterable<string>) {
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
const communityListsSubjects = useMemo(() => {
|
||||
return pubkeys.map((pubkey) =>
|
||||
|
@ -38,7 +38,6 @@ import {
|
||||
getCommunityName,
|
||||
} from "../../helpers/nostr/communities";
|
||||
import NostrPublishAction from "../../classes/nostr-publish-action";
|
||||
import { unique } from "../../helpers/array";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
import replaceableEventLoaderService, { createCoordinate } from "../../services/replaceable-event-requester";
|
||||
import { getImageSize } from "../../helpers/image";
|
||||
@ -52,6 +51,7 @@ import { getEventCoordinate, sortByDate } from "../../helpers/nostr/events";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import ApprovedEvent from "../community/components/community-approved-post";
|
||||
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
|
||||
import RelaySet from "../../classes/relay-set";
|
||||
|
||||
function CommunitiesHomePage() {
|
||||
const toast = useToast();
|
||||
@ -92,7 +92,7 @@ function CommunitiesHomePage() {
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction(
|
||||
"Create Community",
|
||||
unique([...clientRelaysService.getWriteUrls(), ...values.relays]),
|
||||
RelaySet.from(clientRelaysService.writeRelays.value, values.relays),
|
||||
signed,
|
||||
);
|
||||
|
||||
|
@ -71,7 +71,7 @@ export default function CommunityEditModal({
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction(
|
||||
"Update Community",
|
||||
unique([...clientRelaysService.getWriteUrls(), ...values.relays]),
|
||||
unique([...clientRelaysService.outbox.urls, ...values.relays]),
|
||||
signed,
|
||||
);
|
||||
|
||||
|
@ -36,7 +36,7 @@ export default function PostVoteButtons({ event, ...props }: Omit<CardProps, "ch
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
if (signed) {
|
||||
const writeRelays = clientRelaysService.getWriteUrls();
|
||||
const writeRelays = clientRelaysService.outbox.urls;
|
||||
new NostrPublishAction("Reaction", unique([...writeRelays, ...additionalRelays]), signed);
|
||||
eventReactionsService.handleEvent(signed);
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ function useCommunityPointer() {
|
||||
|
||||
export default function CommunityView() {
|
||||
const pointer = useCommunityPointer();
|
||||
const community = useReplaceableEvent(pointer, [], { alwaysRequest: true });
|
||||
const community = useReplaceableEvent(pointer, undefined, { alwaysRequest: true });
|
||||
|
||||
if (!community) return <Spinner />;
|
||||
|
||||
|
@ -21,10 +21,10 @@ import { CheckIcon } from "../../../components/icons";
|
||||
import { useSigningContext } from "../../../providers/global/signing-provider";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||
import { useWriteRelayUrls } from "../../../hooks/use-client-relays";
|
||||
import CommunityPost from "../components/community-post";
|
||||
import { RouterContext } from "../community-home";
|
||||
import useUserMuteFilter from "../../../hooks/use-user-mute-filter";
|
||||
import { useWriteRelayUrls } from "../../../hooks/use-client-relays";
|
||||
|
||||
type PendingProps = {
|
||||
event: NostrEvent;
|
||||
|
@ -10,7 +10,6 @@ import useSubject from "../../hooks/use-subject";
|
||||
import RequireCurrentAccount from "../../providers/route/require-current-account";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
|
||||
@ -24,6 +23,7 @@ import { useRouterMarker } from "../../providers/drawer-sub-view-provider";
|
||||
import TimelineLoader from "../../classes/timeline-loader";
|
||||
import DirectMessageBlock from "./components/direct-message-block";
|
||||
import useParamsProfilePointer from "../../hooks/use-params-pubkey-pointer";
|
||||
import { useUserInbox } from "../../hooks/use-user-mailboxes";
|
||||
|
||||
/** This is broken out from DirectMessageChatPage for performance reasons. Don't use outside of file */
|
||||
const ChatLog = memo(({ timeline }: { timeline: TimelineLoader }) => {
|
||||
@ -70,8 +70,8 @@ function DirectMessageChatPage({ pubkey }: { pubkey: string }) {
|
||||
marker.reset();
|
||||
}, [marker, navigate]);
|
||||
|
||||
const myInbox = useReadRelayUrls();
|
||||
const timeline = useTimelineLoader(`${pubkey}-${account.pubkey}-messages`, myInbox, [
|
||||
const readRelays = useUserInbox(account.pubkey);
|
||||
const timeline = useTimelineLoader(`${pubkey}-${account.pubkey}-messages`, readRelays, [
|
||||
{
|
||||
kinds: [kinds.EncryptedDirectMessage],
|
||||
"#p": [account.pubkey],
|
||||
|
@ -8,12 +8,14 @@ import { useSigningContext } from "../../../providers/global/signing-provider";
|
||||
import MagicTextArea, { RefType } from "../../../components/magic-textarea";
|
||||
import { useTextAreaUploadFileWithForm } from "../../../hooks/use-textarea-upload-file";
|
||||
import clientRelaysService from "../../../services/client-relays";
|
||||
import { unique } from "../../../helpers/array";
|
||||
import { DraftNostrEvent } from "../../../types/nostr-event";
|
||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||
import { useUserRelays } from "../../../hooks/use-user-relays";
|
||||
import { RelayMode } from "../../../classes/relay";
|
||||
import { useDecryptionContext } from "../../../providers/global/dycryption-provider";
|
||||
import useUserMailboxes from "../../../hooks/use-user-mailboxes";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import userMailboxesService from "../../../services/user-mailboxes";
|
||||
import accountService from "../../../services/account";
|
||||
import RelaySet from "../../../classes/relay-set";
|
||||
|
||||
export default function SendMessageForm({
|
||||
pubkey,
|
||||
@ -37,9 +39,7 @@ export default function SendMessageForm({
|
||||
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const { onPaste } = useTextAreaUploadFileWithForm(autocompleteRef, getValues, setValue);
|
||||
|
||||
const usersInbox = useUserRelays(pubkey)
|
||||
.filter((r) => r.mode & RelayMode.READ)
|
||||
.map((r) => r.url);
|
||||
const userMailboxes = useUserMailboxes(pubkey);
|
||||
const sendMessage = handleSubmit(async (values) => {
|
||||
try {
|
||||
if (!values.content) return;
|
||||
@ -59,8 +59,8 @@ export default function SendMessageForm({
|
||||
|
||||
setLoadingMessage("Signing...");
|
||||
const signed = await requestSignature(event);
|
||||
const writeRelays = clientRelaysService.getWriteUrls();
|
||||
const relays = unique([...writeRelays, ...usersInbox]);
|
||||
const relays = RelaySet.from(clientRelaysService.outbox);
|
||||
if (userMailboxes?.inbox) relays.merge(userMailboxes.inbox);
|
||||
new NostrPublishAction("Send DM", relays, signed);
|
||||
reset();
|
||||
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
CloseButton,
|
||||
Code,
|
||||
Heading,
|
||||
Spinner,
|
||||
@ -27,21 +26,18 @@ import { InlineInvoiceCard } from "../../../components/inline-invoice-card";
|
||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||
import { useSigningContext } from "../../../providers/global/signing-provider";
|
||||
import { DraftNostrEvent } from "../../../types/nostr-event";
|
||||
import { unique } from "../../../helpers/array";
|
||||
import clientRelaysService from "../../../services/client-relays";
|
||||
import { useUserRelays } from "../../../hooks/use-user-relays";
|
||||
import { useReadRelayUrls } from "../../../hooks/use-client-relays";
|
||||
import { RelayMode } from "../../../classes/relay";
|
||||
import { DVMAvatarLink } from "./dvm-avatar";
|
||||
import DVMLink from "./dvm-name";
|
||||
import { AddressPointer } from "nostr-tools/lib/types/nip19";
|
||||
import useUserMailboxes from "../../../hooks/use-user-mailboxes";
|
||||
import RelaySet from "../../../classes/relay-set";
|
||||
|
||||
function NextPageButton({ chain, pointer }: { pointer: AddressPointer; chain: ChainedDVMJob[] }) {
|
||||
const toast = useToast();
|
||||
const { requestSignature } = useSigningContext();
|
||||
const dvmRelays = useUserRelays(pointer.pubkey)
|
||||
.filter((r) => r.mode & RelayMode.READ)
|
||||
.map((r) => r.url);
|
||||
const dvmRelays = useUserMailboxes(pointer.pubkey)?.relays;
|
||||
const readRelays = useReadRelayUrls();
|
||||
|
||||
const lastJob = chain[chain.length - 1];
|
||||
@ -60,7 +56,7 @@ function NextPageButton({ chain, pointer }: { pointer: AddressPointer; chain: Ch
|
||||
};
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Next Page", unique([...clientRelaysService.getWriteUrls(), ...dvmRelays]), signed);
|
||||
new NostrPublishAction("Next Page", RelaySet.from(clientRelaysService.outbox, dvmRelays), signed);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ status: "error", description: e.message });
|
||||
}
|
||||
|
@ -31,7 +31,6 @@ import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import { useUserRelays } from "../../hooks/use-user-relays";
|
||||
import { useSigningContext } from "../../providers/global/signing-provider";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import RequireCurrentAccount from "../../providers/route/require-current-account";
|
||||
@ -41,8 +40,9 @@ import DebugChains from "./components/debug-chains";
|
||||
import Feed from "./components/feed";
|
||||
import { AddressPointer } from "nostr-tools/lib/types/nip19";
|
||||
import useParamsAddressPointer from "../../hooks/use-params-address-pointer";
|
||||
import useDVMMetadata from "../../hooks/use-dvm-metadata";
|
||||
import DVMParams from "./components/dvm-params";
|
||||
import useUserMailboxes from "../../hooks/use-user-mailboxes";
|
||||
import RelaySet from "../../classes/relay-set";
|
||||
|
||||
function DVMFeedPage({ pointer }: { pointer: AddressPointer }) {
|
||||
const [since] = useState(() => dayjs().subtract(1, "hour").unix());
|
||||
@ -51,7 +51,7 @@ function DVMFeedPage({ pointer }: { pointer: AddressPointer }) {
|
||||
const account = useCurrentAccount()!;
|
||||
const debugModal = useDisclosure();
|
||||
|
||||
const dvmRelays = useUserRelays(pointer.pubkey).map((r) => r.url);
|
||||
const dvmRelays = useUserMailboxes(pointer.pubkey)?.relays;
|
||||
const readRelays = useReadRelayUrls(dvmRelays);
|
||||
const timeline = useTimelineLoader(`${pointer.kind}:${pointer.pubkey}:${pointer.identifier}-jobs`, readRelays, [
|
||||
{ authors: [account.pubkey], "#p": [pointer.pubkey], kinds: [DVM_CONTENT_DISCOVERY_JOB_KIND], since },
|
||||
@ -89,7 +89,7 @@ function DVMFeedPage({ pointer }: { pointer: AddressPointer }) {
|
||||
};
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Request Feed", unique([...clientRelaysService.getWriteUrls(), ...dvmRelays]), signed);
|
||||
new NostrPublishAction("Request Feed", RelaySet.from(clientRelaysService.outbox, dvmRelays), signed);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ status: "error", description: e.message });
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ export default function EmojiPackCreateModal({ onClose, ...props }: Omit<ModalPr
|
||||
|
||||
try {
|
||||
const signed = await requestSignature(draft);
|
||||
const pub = new NostrPublishAction("Create emoji pack", clientRelaysService.getWriteUrls(), signed);
|
||||
const pub = new NostrPublishAction("Create emoji pack", clientRelaysService.outbox.urls, signed);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
navigate(`/emojis/${getSharableEventAddress(signed)}`);
|
||||
} catch (e) {
|
||||
|
@ -38,7 +38,7 @@ export default function EmojiPackFavoriteButton({
|
||||
const signed = await requestSignature(draft);
|
||||
const pub = new NostrPublishAction(
|
||||
isFavorite ? "Unfavorite Emoji pack" : "Favorite emoji pack",
|
||||
clientRelaysService.getWriteUrls(),
|
||||
clientRelaysService.outbox.urls,
|
||||
signed,
|
||||
);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
|
@ -119,7 +119,7 @@ function EmojiPackPage({ pack }: { pack: NostrEvent }) {
|
||||
};
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
const pub = new NostrPublishAction("Update emoji pack", clientRelaysService.getWriteUrls(), signed);
|
||||
const pub = new NostrPublishAction("Update emoji pack", clientRelaysService.outbox.urls, signed);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
setEditing(false);
|
||||
};
|
||||
|
@ -45,7 +45,7 @@ export default function ListFavoriteButton({
|
||||
setLoading(true);
|
||||
const draft = isFavorite ? listRemoveCoordinate(prev, coordinate) : listAddCoordinate(prev, coordinate);
|
||||
const signed = await requestSignature(draft);
|
||||
const pub = new NostrPublishAction("Favorite list", clientRelaysService.getWriteUrls(), signed);
|
||||
const pub = new NostrPublishAction("Favorite list", clientRelaysService.outbox.urls, signed);
|
||||
replaceableEventLoaderService.handleEvent(signed);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||
|
@ -54,7 +54,7 @@ export default function NewListModal({
|
||||
kind: values.kind,
|
||||
};
|
||||
const signed = await requestSignature(draft);
|
||||
const pub = new NostrPublishAction("Create list", clientRelaysService.getWriteUrls(), signed);
|
||||
const pub = new NostrPublishAction("Create list", clientRelaysService.outbox.urls, signed);
|
||||
|
||||
if (onCreated) onCreated(signed);
|
||||
} catch (e) {
|
||||
|
@ -25,7 +25,7 @@ export default function UserCard({ pubkey, relay, list, ...props }: UserCardProp
|
||||
const handleRemoveFromList = useAsyncErrorHandler(async () => {
|
||||
const draft = listRemovePerson(list, pubkey);
|
||||
const signed = await requestSignature(draft);
|
||||
const pub = new NostrPublishAction("Remove from list", clientRelaysService.getWriteUrls(), signed);
|
||||
const pub = new NostrPublishAction("Remove from list", clientRelaysService.outbox.urls, signed);
|
||||
}, [list, requestSignature]);
|
||||
|
||||
return (
|
||||
|
@ -1,15 +1,11 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ButtonProps,
|
||||
Card,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardProps,
|
||||
Checkbox,
|
||||
CheckboxProps,
|
||||
Flex,
|
||||
Heading,
|
||||
IconButton,
|
||||
@ -31,11 +27,7 @@ import { RelayFavicon } from "../../../components/relay-favicon";
|
||||
import { CodeIcon } from "../../../components/icons";
|
||||
import UserLink from "../../../components/user-link";
|
||||
import UserAvatar from "../../../components/user-avatar";
|
||||
import { useClientRelays } from "../../../hooks/use-client-relays";
|
||||
import clientRelaysService from "../../../services/client-relays";
|
||||
import { RelayMode } from "../../../classes/relay";
|
||||
import { UserDnsIdentityIcon } from "../../../components/user-dns-identity-icon";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import RawJson from "../../../components/debug-modals/raw-json";
|
||||
import { RelayShareButton } from "./relay-share-button";
|
||||
import useRelayStats from "../../../hooks/use-relay-stats";
|
||||
@ -78,52 +70,52 @@ export function RelayMetadata({ url, extended }: { url: string; extended?: boole
|
||||
);
|
||||
}
|
||||
|
||||
export function RelayJoinAction({ url, ...props }: { url: string } & Omit<ButtonProps, "children" | "onClick">) {
|
||||
const account = useCurrentAccount();
|
||||
const clientRelays = useClientRelays();
|
||||
const relayConfig = clientRelays.find((r) => r.url === url);
|
||||
// export function RelayJoinAction({ url, ...props }: { url: string } & Omit<ButtonProps, "children" | "onClick">) {
|
||||
// const account = useCurrentAccount();
|
||||
// const clientRelays = useClientRelays();
|
||||
// const relayConfig = clientRelays.find((r) => r.url === url);
|
||||
|
||||
return relayConfig ? (
|
||||
<Button
|
||||
colorScheme="red"
|
||||
variant="outline"
|
||||
onClick={() => clientRelaysService.removeRelay(url)}
|
||||
isDisabled={!account}
|
||||
{...props}
|
||||
>
|
||||
Leave
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
colorScheme="green"
|
||||
onClick={() => clientRelaysService.addRelay(url, RelayMode.ALL)}
|
||||
isDisabled={!account}
|
||||
{...props}
|
||||
>
|
||||
Join
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
// return relayConfig ? (
|
||||
// <Button
|
||||
// colorScheme="red"
|
||||
// variant="outline"
|
||||
// onClick={() => clientRelaysService.removeRelay(url)}
|
||||
// isDisabled={!account}
|
||||
// {...props}
|
||||
// >
|
||||
// Leave
|
||||
// </Button>
|
||||
// ) : (
|
||||
// <Button
|
||||
// colorScheme="green"
|
||||
// onClick={() => clientRelaysService.addRelay(url, RelayMode.ALL)}
|
||||
// isDisabled={!account}
|
||||
// {...props}
|
||||
// >
|
||||
// Join
|
||||
// </Button>
|
||||
// );
|
||||
// }
|
||||
|
||||
export function RelayModeAction({
|
||||
url,
|
||||
...props
|
||||
}: { url: string } & Omit<CheckboxProps, "children" | "isChecked" | "onChange">) {
|
||||
const clientRelays = useClientRelays();
|
||||
const relayConfig = clientRelays.find((r) => r.url === url);
|
||||
// export function RelayModeAction({
|
||||
// url,
|
||||
// ...props
|
||||
// }: { url: string } & Omit<CheckboxProps, "children" | "isChecked" | "onChange">) {
|
||||
// const clientRelays = useClientRelays();
|
||||
// const relayConfig = clientRelays.find((r) => r.url === url);
|
||||
|
||||
return relayConfig ? (
|
||||
<Checkbox
|
||||
isChecked={!!(relayConfig.mode & RelayMode.WRITE)}
|
||||
onChange={(e) => {
|
||||
clientRelaysService.updateRelay(relayConfig.url, e.target.checked ? RelayMode.WRITE : RelayMode.READ);
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
Write
|
||||
</Checkbox>
|
||||
) : null;
|
||||
}
|
||||
// return relayConfig ? (
|
||||
// <Checkbox
|
||||
// isChecked={!!(relayConfig.mode & RelayMode.WRITE)}
|
||||
// onChange={(e) => {
|
||||
// clientRelaysService.updateRelay(relayConfig.url, e.target.checked ? RelayMode.WRITE : RelayMode.READ);
|
||||
// }}
|
||||
// {...props}
|
||||
// >
|
||||
// Write
|
||||
// </Checkbox>
|
||||
// ) : null;
|
||||
// }
|
||||
|
||||
export function RelayDebugButton({ url, ...props }: { url: string } & Omit<IconButtonProps, "icon" | "aria-label">) {
|
||||
const { info } = useRelayInfo(url);
|
||||
@ -174,8 +166,8 @@ export default function RelayCard({ url, ...props }: { url: string } & Omit<Card
|
||||
<RelayMetadata url={url} />
|
||||
</CardBody>
|
||||
<CardFooter p="2" as={Flex} gap="2">
|
||||
<RelayJoinAction url={url} size="sm" />
|
||||
<RelayModeAction url={url} />
|
||||
{/* <RelayJoinAction url={url} size="sm" /> */}
|
||||
{/* <RelayModeAction url={url} /> */}
|
||||
|
||||
<RelayShareButton relay={url} ml="auto" size="sm" />
|
||||
<RelayDebugButton url={url} size="sm" title="Show raw NIP-11 metadata" />
|
||||
|
@ -17,7 +17,7 @@ export function RelayShareButton({
|
||||
|
||||
const recommendRelay = useCallback(async () => {
|
||||
try {
|
||||
const writeRelays = clientRelaysService.getWriteUrls();
|
||||
const writeRelays = clientRelaysService.outbox.urls;
|
||||
|
||||
const draft: DraftNostrEvent = {
|
||||
kind: 2,
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useDeferredValue, useMemo, useState } from "react";
|
||||
import { useAsync } from "react-use";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { Button, Divider, Flex, Heading, Input, SimpleGrid, Spacer, Switch, useDisclosure } from "@chakra-ui/react";
|
||||
import { Button, Flex, Heading, Input, SimpleGrid, Spacer, useDisclosure } from "@chakra-ui/react";
|
||||
|
||||
import { useClientRelays } from "../../hooks/use-client-relays";
|
||||
import relayPoolService from "../../services/relay-pool";
|
||||
import AddCustomRelayModal from "./components/add-custom-modal";
|
||||
import RelayCard from "./components/relay-card";
|
||||
@ -12,6 +11,7 @@ import { RelayMode } from "../../classes/relay";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import { isValidRelayURL } from "../../helpers/relay";
|
||||
import { useReadRelayUrls, useWriteRelayUrls } from "../../hooks/use-client-relays";
|
||||
|
||||
export default function RelaysView() {
|
||||
const [search, setSearch] = useState("");
|
||||
@ -19,10 +19,11 @@ export default function RelaysView() {
|
||||
const isSearching = deboundedSearch.length > 2;
|
||||
const addRelayModal = useDisclosure();
|
||||
|
||||
const clientRelays = useClientRelays().map((r) => r.url);
|
||||
const readRelays = useReadRelayUrls();
|
||||
const writeRelays = useWriteRelayUrls();
|
||||
const discoveredRelays = relayPoolService
|
||||
.getRelays()
|
||||
.filter((r) => !clientRelays.includes(r.url))
|
||||
.filter((r) => !readRelays.has(r.url) && !writeRelays.has(r.url))
|
||||
.map((r) => r.url)
|
||||
.filter(isValidRelayURL);
|
||||
|
||||
@ -32,11 +33,11 @@ export default function RelaysView() {
|
||||
|
||||
const filteredRelays = useMemo(() => {
|
||||
if (isSearching) {
|
||||
return onlineRelays.filter((url) => url.includes(deboundedSearch));
|
||||
return onlineRelays.filter((url) => url.toLowerCase().includes(deboundedSearch.toLowerCase()));
|
||||
}
|
||||
|
||||
return clientRelays;
|
||||
}, [isSearching, deboundedSearch, onlineRelays, clientRelays]);
|
||||
return [...readRelays];
|
||||
}, [isSearching, deboundedSearch, onlineRelays, readRelays]);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
|
@ -16,12 +16,12 @@ import { Link as RouterLink, useNavigate } from "react-router-dom";
|
||||
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import { getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||
import { useClientRelays, useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import useSubjects from "../../hooks/use-subjects";
|
||||
import useUserContactList from "../../hooks/use-user-contact-list";
|
||||
import RequireCurrentAccount from "../../providers/route/require-current-account";
|
||||
import userRelaysService from "../../services/user-relays";
|
||||
import userMailboxesService from "../../services/user-mailboxes";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { RelayFavicon } from "../../components/relay-favicon";
|
||||
import { ChevronLeftIcon } from "../../components/icons";
|
||||
@ -30,12 +30,14 @@ import { RelayMetadata, RelayPaidTag } from "./components/relay-card";
|
||||
|
||||
function usePopularContactsRelays(list?: NostrEvent) {
|
||||
const readRelays = useReadRelayUrls();
|
||||
const subs = list ? getPubkeysFromList(list).map((p) => userRelaysService.requestRelays(p.pubkey, readRelays)) : [];
|
||||
const subs = list
|
||||
? getPubkeysFromList(list).map((p) => userMailboxesService.requestMailboxes(p.pubkey, readRelays))
|
||||
: [];
|
||||
const contactsRelays = useSubjects(subs);
|
||||
|
||||
const relayScore: Record<string, string[]> = {};
|
||||
for (const { relays, pubkey } of contactsRelays) {
|
||||
for (const { url } of relays) {
|
||||
for (const url of relays) {
|
||||
relayScore[url] = relayScore[url] || [];
|
||||
relayScore[url].push(pubkey);
|
||||
}
|
||||
@ -76,10 +78,7 @@ function PopularRelaysPage() {
|
||||
const account = useCurrentAccount();
|
||||
const contacts = useUserContactList(account?.pubkey);
|
||||
|
||||
const clientRelays = useClientRelays().map((r) => r.url);
|
||||
const popularRelays = usePopularContactsRelays(contacts).filter(
|
||||
(r) => !clientRelays.includes(r.url) && r.pubkeys.length > 1,
|
||||
);
|
||||
const popularRelays = usePopularContactsRelays(contacts).filter((r) => r.pubkeys.length > 1);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
import { useRelayInfo } from "../../../hooks/use-relay-info";
|
||||
import { RelayDebugButton, RelayJoinAction, RelayMetadata } from "../components/relay-card";
|
||||
import { RelayDebugButton, RelayMetadata } from "../components/relay-card";
|
||||
import SupportedNIPs from "../components/supported-nips";
|
||||
import { ExternalLinkIcon } from "../../../components/icons";
|
||||
import RelayReviewForm from "./relay-review-form";
|
||||
@ -63,7 +63,7 @@ function RelayPage({ relay }: { relay: string }) {
|
||||
>
|
||||
More info
|
||||
</Button>
|
||||
<RelayJoinAction url={relay} />
|
||||
{/* <RelayJoinAction url={relay} /> */}
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
<RelayMetadata url={relay} extended />
|
||||
|
@ -10,9 +10,8 @@ import { nostrBuildUploadImage } from "../../helpers/nostr-build";
|
||||
import NostrPublishAction from "../../classes/nostr-publish-action";
|
||||
import accountService from "../../services/account";
|
||||
import signingService from "../../services/signing";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
import { RelayMode } from "../../classes/relay";
|
||||
import { COMMON_CONTACT_RELAY } from "../../const";
|
||||
import { DraftNostrEvent } from "../../types/nostr-event";
|
||||
|
||||
export default function CreateStep({
|
||||
metadata,
|
||||
@ -68,7 +67,14 @@ export default function CreateStep({
|
||||
accountService.switchAccount(pubkey);
|
||||
|
||||
// set relays
|
||||
await clientRelaysService.postUpdatedRelays(relays.map((url) => ({ url, mode: RelayMode.ALL })));
|
||||
const draft: DraftNostrEvent = {
|
||||
kind: kinds.RelayList,
|
||||
content: "",
|
||||
tags: relays.map((url) => ["r", url]),
|
||||
created_at: dayjs().unix(),
|
||||
};
|
||||
const signed = finalizeEvent(draft, hex);
|
||||
new NostrPublishAction("Set Mailbox Relays", relays, signed);
|
||||
|
||||
onSubmit(bytesToHex(hex));
|
||||
} catch (e) {
|
||||
|
@ -1,16 +1,5 @@
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
CardProps,
|
||||
Flex,
|
||||
Heading,
|
||||
Image,
|
||||
LinkBox,
|
||||
LinkOverlay,
|
||||
} from "@chakra-ui/react";
|
||||
import { Card, CardBody, CardHeader, CardProps, Heading, Image, LinkBox, LinkOverlay } from "@chakra-ui/react";
|
||||
|
||||
import { useReadRelayUrls } from "../../../hooks/use-client-relays";
|
||||
import { useRelaySelectionRelays } from "../../../providers/local/relay-selection-provider";
|
||||
@ -24,10 +13,10 @@ import OpenGraphCard from "../../../components/open-graph-card";
|
||||
export const STREAMER_CARDS_TYPE = 17777;
|
||||
export const STREAMER_CARD_TYPE = 37777;
|
||||
|
||||
function useStreamerCardsCords(pubkey: string, relays: string[]) {
|
||||
function useStreamerCardsCords(pubkey: string, relays: Iterable<string>) {
|
||||
const sub = useMemo(
|
||||
() => replaceableEventLoaderService.requestEvent(relays, STREAMER_CARDS_TYPE, pubkey),
|
||||
[pubkey, relays.join("|")],
|
||||
[pubkey, relays],
|
||||
);
|
||||
const streamerCards = useSubject(sub);
|
||||
|
||||
|
@ -4,8 +4,6 @@ import { useForm } from "react-hook-form";
|
||||
|
||||
import { ParsedStream, buildChatMessage } from "../../../../helpers/nostr/stream";
|
||||
import { useRelaySelectionRelays } from "../../../../providers/local/relay-selection-provider";
|
||||
import { useUserRelays } from "../../../../hooks/use-user-relays";
|
||||
import { RelayMode } from "../../../../classes/relay";
|
||||
import { unique } from "../../../../helpers/array";
|
||||
import { useSigningContext } from "../../../../providers/global/signing-provider";
|
||||
import NostrPublishAction from "../../../../classes/nostr-publish-action";
|
||||
@ -14,14 +12,13 @@ import { useContextEmojis } from "../../../../providers/global/emoji-provider";
|
||||
import { MagicInput, RefType } from "../../../../components/magic-textarea";
|
||||
import StreamZapButton from "../../components/stream-zap-button";
|
||||
import { nostrBuildUploadImage } from "../../../../helpers/nostr-build";
|
||||
import { useUserInbox } from "../../../../hooks/use-user-mailboxes";
|
||||
|
||||
export default function ChatMessageForm({ stream, hideZapButton }: { stream: ParsedStream; hideZapButton?: boolean }) {
|
||||
const toast = useToast();
|
||||
const emojis = useContextEmojis();
|
||||
const streamRelays = useRelaySelectionRelays();
|
||||
const hostReadRelays = useUserRelays(stream.host)
|
||||
.filter((r) => r.mode & RelayMode.READ)
|
||||
.map((r) => r.url);
|
||||
const hostReadRelays = useUserInbox(stream.host);
|
||||
|
||||
const relays = useMemo(() => unique([...streamRelays, ...hostReadRelays]), [hostReadRelays, streamRelays]);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ReactNode, useMemo } from "react";
|
||||
import { Card, Flex, Heading, Link, Spinner } from "@chakra-ui/react";
|
||||
import { Card, Heading, Link, Spinner } from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
|
@ -25,7 +25,7 @@ import { useUserMetadata } from "../../hooks/use-user-metadata";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ChevronLeftIcon } from "../../components/icons";
|
||||
|
||||
export function useUsersMuteLists(pubkeys: string[], additionalRelays: string[] = []) {
|
||||
export function useUsersMuteLists(pubkeys: string[], additionalRelays?: Iterable<string>) {
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
const muteListSubjects = useMemo(() => {
|
||||
return pubkeys.map((pubkey) => replaceableEventLoaderService.requestEvent(readRelays, MUTE_LIST_KIND, pubkey));
|
||||
|
@ -44,7 +44,7 @@ export default function NoteTextToSpeechPage({ note }: { note: NostrEvent }) {
|
||||
};
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Request Reading", clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction("Request Reading", clientRelaysService.outbox.urls, signed);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ status: "error", description: e.message });
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ export function NoteTranslationsPage({ note }: { note: NostrEvent }) {
|
||||
};
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Request Translation", clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction("Request Translation", clientRelaysService.outbox.urls, signed);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ status: "error", description: e.message });
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ export default function NewTorrentView() {
|
||||
};
|
||||
|
||||
const signed = await requestSignature(draft);
|
||||
new NostrPublishAction("Publish Torrent", clientRelaysService.getWriteUrls(), signed);
|
||||
new NostrPublishAction("Publish Torrent", clientRelaysService.outbox.urls, signed);
|
||||
|
||||
navigate(`/torrents/${nip19.noteEncode(signed.id)}`);
|
||||
} catch (e) {
|
||||
|
@ -17,14 +17,13 @@ import {
|
||||
import accountService from "../../../services/account";
|
||||
import { useUserMetadata } from "../../../hooks/use-user-metadata";
|
||||
import { getUserDisplayName } from "../../../helpers/user-metadata";
|
||||
import { useUserRelays } from "../../../hooks/use-user-relays";
|
||||
import { RelayMode } from "../../../classes/relay";
|
||||
import UserDebugModal from "../../../components/debug-modals/user-debug-modal";
|
||||
import { useSharableProfileId } from "../../../hooks/use-shareable-profile-id";
|
||||
import { buildAppSelectUrl } from "../../../helpers/nostr/apps";
|
||||
import { truncatedId } from "../../../helpers/nostr/events";
|
||||
import useUserMuteActions from "../../../hooks/use-user-mute-actions";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import userMailboxesService from "../../../services/user-mailboxes";
|
||||
|
||||
export const UserProfileMenu = ({
|
||||
pubkey,
|
||||
@ -34,18 +33,17 @@ export const UserProfileMenu = ({
|
||||
const toast = useToast();
|
||||
const account = useCurrentAccount();
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const userRelays = useUserRelays(pubkey);
|
||||
const infoModal = useDisclosure();
|
||||
const sharableId = useSharableProfileId(pubkey);
|
||||
const { isMuted, mute, unmute } = useUserMuteActions(pubkey);
|
||||
|
||||
const loginAsUser = () => {
|
||||
const readRelays = userRelays.filter((r) => r.mode === RelayMode.READ).map((r) => r.url) ?? [];
|
||||
const relays = userMailboxesService.getMailboxes(pubkey).value?.outbox.urls;
|
||||
if (!accountService.hasAccount(pubkey)) {
|
||||
accountService.addAccount({
|
||||
type: "pubkey",
|
||||
pubkey,
|
||||
relays: readRelays,
|
||||
relays,
|
||||
readonly: true,
|
||||
});
|
||||
}
|
||||
|
@ -33,11 +33,9 @@ import { getUserDisplayName } from "../../helpers/user-metadata";
|
||||
import { useAppTitle } from "../../hooks/use-app-title";
|
||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import relayScoreboardService from "../../services/relay-scoreboard";
|
||||
import { RelayMode } from "../../classes/relay";
|
||||
import { AdditionalRelayProvider } from "../../providers/local/additional-relay-context";
|
||||
import { unique } from "../../helpers/array";
|
||||
import { RelayFavicon } from "../../components/relay-favicon";
|
||||
import { useUserRelays } from "../../hooks/use-user-relays";
|
||||
import Header from "./components/header";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import useEventExists from "../../hooks/use-event-exists";
|
||||
@ -46,6 +44,7 @@ import { STREAM_KIND } from "../../helpers/nostr/stream";
|
||||
import { TORRENT_KIND } from "../../helpers/nostr/torrents";
|
||||
import { GOAL_KIND } from "../../helpers/nostr/goal";
|
||||
import useParamsProfilePointer from "../../hooks/use-params-pubkey-pointer";
|
||||
import useUserMailboxes from "../../hooks/use-user-mailboxes";
|
||||
|
||||
const tabs = [
|
||||
{ label: "About", path: "about" },
|
||||
@ -67,16 +66,10 @@ const tabs = [
|
||||
{ label: "Muted by", path: "muted-by" },
|
||||
];
|
||||
|
||||
function useUserTopRelays(pubkey: string, count: number = 4) {
|
||||
const readRelays = useReadRelayUrls();
|
||||
// get user relays
|
||||
const userRelays = useUserRelays(pubkey, readRelays)
|
||||
.filter((r) => r.mode & RelayMode.WRITE)
|
||||
.map((r) => r.url);
|
||||
// merge the users relays with client relays
|
||||
if (userRelays.length === 0) return readRelays;
|
||||
const sorted = relayScoreboardService.getRankedRelays(userRelays);
|
||||
|
||||
function useUserBestOutbox(pubkey: string, count: number = 4) {
|
||||
const mailbox = useUserMailboxes(pubkey);
|
||||
const relays = useReadRelayUrls(mailbox?.outbox);
|
||||
const sorted = relayScoreboardService.getRankedRelays(relays);
|
||||
return !count ? sorted : sorted.slice(0, count);
|
||||
}
|
||||
|
||||
@ -84,7 +77,7 @@ const UserView = () => {
|
||||
const { pubkey, relays: pointerRelays = [] } = useParamsProfilePointer();
|
||||
const navigate = useNavigate();
|
||||
const [relayCount, setRelayCount] = useState(4);
|
||||
const userTopRelays = useUserTopRelays(pubkey, relayCount);
|
||||
const userTopRelays = useUserBestOutbox(pubkey, relayCount);
|
||||
const relayModal = useDisclosure();
|
||||
const readRelays = unique([...userTopRelays, ...pointerRelays]);
|
||||
|
||||
|
@ -1,20 +1,19 @@
|
||||
import { useOutletContext, Link as RouterLink } from "react-router-dom";
|
||||
import { Button, Flex, Heading, Spacer, StackDivider, Tag, VStack } from "@chakra-ui/react";
|
||||
|
||||
import { useUserRelays } from "../../hooks/use-user-relays";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { truncatedId } from "../../helpers/nostr/events";
|
||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import RelayReviewNote from "../relays/components/relay-review-note";
|
||||
import { RelayFavicon } from "../../components/relay-favicon";
|
||||
import { RelayDebugButton, RelayJoinAction, RelayMetadata } from "../relays/components/relay-card";
|
||||
import { RelayDebugButton, RelayMetadata } from "../relays/components/relay-card";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import { useRelayInfo } from "../../hooks/use-relay-info";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import { RelayShareButton } from "../relays/components/relay-share-button";
|
||||
import useUserMailboxes from "../../hooks/use-user-mailboxes";
|
||||
|
||||
function Relay({ url, reviews }: { url: string; reviews: NostrEvent[] }) {
|
||||
const { info } = useRelayInfo(url);
|
||||
@ -37,7 +36,7 @@ function Relay({ url, reviews }: { url: string; reviews: NostrEvent[] }) {
|
||||
<Button as={RouterLink} to={`/global?relay=${url}`} size="sm">
|
||||
Notes
|
||||
</Button>
|
||||
<RelayJoinAction url={url} size="sm" />
|
||||
{/* <RelayJoinAction url={url} size="sm" /> */}
|
||||
</Flex>
|
||||
<RelayMetadata url={url} />
|
||||
{reviews.length > 0 && (
|
||||
@ -57,9 +56,9 @@ function getRelayReviews(url: string, events: NostrEvent[]) {
|
||||
|
||||
const UserRelaysTab = () => {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const userRelays = useUserRelays(pubkey);
|
||||
const mailboxes = useUserMailboxes(pubkey);
|
||||
|
||||
const readRelays = useReadRelayUrls(userRelays.map((r) => r.url));
|
||||
const readRelays = useReadRelayUrls(mailboxes?.outbox);
|
||||
const timeline = useTimelineLoader(`${pubkey}-relay-reviews`, readRelays, {
|
||||
authors: [pubkey],
|
||||
kinds: [1985],
|
||||
@ -72,21 +71,36 @@ const UserRelaysTab = () => {
|
||||
|
||||
const otherReviews = reviews.filter((e) => {
|
||||
const url = e.tags.find((t) => t[0] === "r")?.[1];
|
||||
return !userRelays.some((r) => r.url === url);
|
||||
return url && !mailboxes?.relays.has(url);
|
||||
});
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Heading size="lg" ml="2" mt="2">
|
||||
Inboxes
|
||||
</Heading>
|
||||
<VStack divider={<StackDivider />} py="2" align="stretch">
|
||||
{userRelays.map((relayConfig) => (
|
||||
{mailboxes?.inbox.urls.map((url) => (
|
||||
<ErrorBoundary>
|
||||
<Relay key={relayConfig.url} url={relayConfig.url} reviews={getRelayReviews(relayConfig.url, reviews)} />
|
||||
<Relay key={url} url={url} reviews={getRelayReviews(url, reviews)} />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</VStack>
|
||||
<Heading size="lg" ml="2" mt="2">
|
||||
Outboxes
|
||||
</Heading>
|
||||
<VStack divider={<StackDivider />} py="2" align="stretch">
|
||||
{mailboxes?.outbox.urls.map((url) => (
|
||||
<ErrorBoundary>
|
||||
<Relay key={url} url={url} reviews={getRelayReviews(url, reviews)} />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</VStack>
|
||||
{otherReviews.length > 0 && (
|
||||
<>
|
||||
<Heading>Other Reviews</Heading>
|
||||
<Heading size="lg" ml="2" mt="2">
|
||||
Reviews
|
||||
</Heading>
|
||||
<Flex direction="column" gap="2" pb="8">
|
||||
{otherReviews.map((event) => (
|
||||
<RelayReviewNote key={event.id} event={event} />
|
||||
|
Loading…
x
Reference in New Issue
Block a user