mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-11 13:20:37 +02:00
cleanup
This commit is contained in:
parent
a873d6fdb7
commit
5553bce0fe
15
README.md
15
README.md
@ -52,23 +52,20 @@
|
||||
|
||||
## TODO
|
||||
|
||||
- add `client` tag to published events
|
||||
- Rebuild relays view to show relay info and settings NIP-11
|
||||
- add button for creating lightning invoice via WebLN
|
||||
- make app a valid web share target https://developer.chrome.com/articles/web-share-target/
|
||||
- make app handle image files
|
||||
- block notes based on content
|
||||
- implement NIP-56 and blocking
|
||||
- allow user to select relay or following list when fetching replies (default to my relays + following?)
|
||||
- massive thread note1dapvuu8fl09yjtg2gyr2h6nypaffl2sq0aj5raz86463qk5kpyzqlxvtc3
|
||||
- sort replies by date
|
||||
- filter list of followers by users the user has blocked/reported (stops bots/spammers from showing up at followers)
|
||||
- Add client side relay groups
|
||||
- Add mentions in posts (https://css-tricks.com/so-you-want-to-build-an-mention-autocomplete-feature/)
|
||||
- Add note embeds
|
||||
- Add "repost" button that mentions the note
|
||||
- Add preview tab to note modal
|
||||
- Add mentions in posts (https://css-tricks.com/so-you-want-to-build-an-mention-autocomplete-feature/)
|
||||
- add `client` tag to published events
|
||||
- Save note drafts and let users manage them
|
||||
- Add support for relay favicons
|
||||
- make app a valid web share target https://developer.chrome.com/articles/web-share-target/
|
||||
- implement NIP-56 and blocking
|
||||
- block notes based on content
|
||||
|
||||
## Setup
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Subject, Subscription } from "rxjs";
|
||||
import { relayPool } from "../services/relays";
|
||||
import relayPoolService from "../services/relay-pool";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
|
||||
export type PostResult = { url: string; message?: string; status: boolean };
|
||||
@ -9,7 +9,7 @@ export function nostrPostAction(relays: string[], event: NostrEvent, timeout: nu
|
||||
let remaining = new Set<Subscription>();
|
||||
|
||||
for (const url of relays) {
|
||||
const relay = relayPool.requestRelay(url);
|
||||
const relay = relayPoolService.requestRelay(url);
|
||||
|
||||
const sub = relay.onCommandResult.subscribe((result) => {
|
||||
if (result.eventId === event.id) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Subject, Subscription as RxSubscription } from "rxjs";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { NostrQuery } from "../types/nostr-query";
|
||||
import { Relay } from "../services/relays";
|
||||
import relayPool from "../services/relays/relay-pool";
|
||||
import relayPoolService from "../services/relay-pool";
|
||||
import { Relay } from "./relay";
|
||||
|
||||
let lastId = 0;
|
||||
|
||||
@ -22,7 +22,7 @@ export class NostrRequest {
|
||||
|
||||
constructor(relayUrls: string[], timeout?: number) {
|
||||
this.id = `request-${lastId++}`;
|
||||
this.relays = new Set(relayUrls.map((url) => relayPool.requestRelay(url)));
|
||||
this.relays = new Set(relayUrls.map((url) => relayPoolService.requestRelay(url)));
|
||||
|
||||
for (const relay of this.relays) {
|
||||
const cleanup: RxSubscription[] = [];
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { Subject, SubscriptionLike } from "rxjs";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { NostrOutgoingMessage, NostrQuery } from "../types/nostr-query";
|
||||
import { Relay } from "../services/relays";
|
||||
import { IncomingEvent } from "../services/relays/relay";
|
||||
import relayPool from "../services/relays/relay-pool";
|
||||
import { IncomingEvent, Relay } from "./relay";
|
||||
import relayPoolService from "../services/relay-pool";
|
||||
|
||||
let lastId = 0;
|
||||
|
||||
@ -27,7 +26,7 @@ export class NostrSubscription {
|
||||
this.name = name;
|
||||
this.relayUrls = relayUrls;
|
||||
|
||||
this.relays = relayUrls.map((url) => relayPool.requestRelay(url));
|
||||
this.relays = relayUrls.map((url) => relayPoolService.requestRelay(url));
|
||||
}
|
||||
private handleEvent(event: IncomingEvent) {
|
||||
if (this.state === NostrSubscription.OPEN && event.subId === this.id && !this.seenEvents.has(event.body.id)) {
|
||||
@ -51,7 +50,7 @@ export class NostrSubscription {
|
||||
}
|
||||
|
||||
for (const url of this.relayUrls) {
|
||||
relayPool.addClaim(url, this);
|
||||
relayPoolService.addClaim(url, this);
|
||||
}
|
||||
}
|
||||
/** listen for event and open events from relays */
|
||||
@ -60,7 +59,7 @@ export class NostrSubscription {
|
||||
this.cleanup.clear();
|
||||
|
||||
for (const url of this.relayUrls) {
|
||||
relayPool.removeClaim(url, this);
|
||||
relayPoolService.removeClaim(url, this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +87,7 @@ export class NostrSubscription {
|
||||
}
|
||||
setRelays(relays: string[]) {
|
||||
this.unsubscribeFromRelays();
|
||||
const newRelays = relays.map((url) => relayPool.requestRelay(url));
|
||||
const newRelays = relays.map((url) => relayPoolService.requestRelay(url));
|
||||
|
||||
for (const relay of this.relays) {
|
||||
if (!newRelays.includes(relay)) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Subject } from "rxjs";
|
||||
import { RawIncomingNostrEvent, NostrEvent } from "../../types/nostr-event";
|
||||
import { NostrOutgoingMessage } from "../../types/nostr-query";
|
||||
import { RawIncomingNostrEvent, NostrEvent } from "../types/nostr-event";
|
||||
import { NostrOutgoingMessage } from "../types/nostr-query";
|
||||
|
||||
export type IncomingEvent = {
|
||||
type: "EVENT";
|
@ -1,93 +0,0 @@
|
||||
import { Subject } from "rxjs";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { NostrQuery } from "../types/nostr-query";
|
||||
import { NostrRequest } from "./nostr-request";
|
||||
|
||||
function mergeSets<T extends unknown>(to: Set<T>, from: Iterable<T>) {
|
||||
for (const el of from) {
|
||||
to.add(el);
|
||||
}
|
||||
}
|
||||
|
||||
export type getQueryKeyFn<QueryT> = (query: QueryT) => string;
|
||||
export type mergeQueriesFn<QueryT> = (a: QueryT, b: QueryT) => QueryT | undefined | null;
|
||||
export type getEventQueryKeyFn = (event: NostrEvent) => string;
|
||||
|
||||
type PendingRequest<QueryT = NostrQuery> = {
|
||||
query: QueryT;
|
||||
subject: Subject<NostrEvent>;
|
||||
relays: Set<string>;
|
||||
};
|
||||
|
||||
/** @deprecated incomplete */
|
||||
export class RequestManager<QueryT extends NostrQuery> {
|
||||
private getQueryKey: getQueryKeyFn<QueryT>;
|
||||
private mergeQueries: mergeQueriesFn<QueryT>;
|
||||
private getEventQueryKey: getEventQueryKeyFn;
|
||||
|
||||
private runningRequests = new Map<string, NostrRequest>();
|
||||
private requestQueue = new Map<string, PendingRequest<QueryT>>();
|
||||
|
||||
constructor(
|
||||
getQueryKey: getQueryKeyFn<QueryT>,
|
||||
mergeQueries: mergeQueriesFn<QueryT>,
|
||||
getEventQueryKey: getEventQueryKeyFn
|
||||
) {
|
||||
this.getQueryKey = getQueryKey;
|
||||
this.mergeQueries = mergeQueries;
|
||||
this.getEventQueryKey = getEventQueryKey;
|
||||
}
|
||||
|
||||
request(query: QueryT, relays: string[]) {
|
||||
const key = this.getQueryKey(query);
|
||||
if (this.runningRequests.has(key)) throw new Error("requesting a currently running query");
|
||||
|
||||
const pending = this.requestQueue.get(key);
|
||||
if (pending) {
|
||||
mergeSets(pending.relays, relays);
|
||||
return pending.subject;
|
||||
}
|
||||
|
||||
const subject = new Subject<NostrEvent>();
|
||||
this.requestQueue.set(key, {
|
||||
query,
|
||||
relays: new Set(relays),
|
||||
subject,
|
||||
});
|
||||
|
||||
return subject;
|
||||
}
|
||||
|
||||
batch() {
|
||||
const requests: PendingRequest<QueryT>[] = [];
|
||||
|
||||
for (const [key, pending] of this.requestQueue) {
|
||||
let wasMerged = false;
|
||||
if (requests.length > 0) {
|
||||
for (const request of requests) {
|
||||
const merged = this.mergeQueries(request.query, pending.query);
|
||||
if (merged) {
|
||||
request.query = merged;
|
||||
request.subject.subscribe(pending.subject);
|
||||
mergeSets(request.relays, pending.relays);
|
||||
wasMerged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there are no requests. or pending failed to merge create new request
|
||||
if (!wasMerged) {
|
||||
const subject = new Subject<NostrEvent>();
|
||||
subject.subscribe(pending.subject);
|
||||
requests.push({ query: pending.query, subject, relays: pending.relays });
|
||||
}
|
||||
}
|
||||
|
||||
for (const request of requests) {
|
||||
const r = new NostrRequest(Array.from(request.relays));
|
||||
r.onEvent.subscribe(request.subject);
|
||||
r.start(request.query);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,20 +10,20 @@ import {
|
||||
ModalCloseButton,
|
||||
Button,
|
||||
} from "@chakra-ui/react";
|
||||
import { Relay } from "../services/relays";
|
||||
import relayPool from "../services/relays/relay-pool";
|
||||
import relayPoolService from "../services/relay-pool";
|
||||
import { useInterval } from "react-use";
|
||||
import { RelayStatus } from "./relay-status";
|
||||
import { useIsMobile } from "../hooks/use-is-mobile";
|
||||
import { RelayIcon } from "./icons";
|
||||
import { Relay } from "../classes/relay";
|
||||
|
||||
export const ConnectedRelays = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const [relays, setRelays] = useState<Relay[]>(relayPool.getRelays());
|
||||
const [relays, setRelays] = useState<Relay[]>(relayPoolService.getRelays());
|
||||
|
||||
useInterval(() => {
|
||||
setRelays(relayPool.getRelays());
|
||||
setRelays(relayPoolService.getRelays());
|
||||
}, 1000);
|
||||
|
||||
const connected = relays.filter((relay) => relay.okay);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { Box, Button, ButtonGroup, Heading, IconButton, Text } from "@chakra-ui/react";
|
||||
import { Box, Button, ButtonGroup, IconButton, Text } from "@chakra-ui/react";
|
||||
import { requestProvider } from "webln";
|
||||
import { getReadableAmount, parsePaymentRequest } from "../helpers/bolt11";
|
||||
import { useAsync } from "react-use";
|
||||
|
@ -16,11 +16,11 @@ import { nostrPostAction } from "../../classes/nostr-post-action";
|
||||
import { NostrRequest } from "../../classes/nostr-request";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { getEventRelays, handleEventFromRelay } from "../../services/event-relays";
|
||||
import { relayPool } from "../../services/relays";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { RelayIcon, SearchIcon } from "../icons";
|
||||
import { RelayFavicon } from "../relay-favicon";
|
||||
import { useReadRelayUrls, useWriteRelayUrls } from "../../hooks/use-client-relays";
|
||||
import relayPoolService from "../../services/relay-pool";
|
||||
|
||||
export type NoteRelaysProps = Omit<IconButtonProps, "icon" | "aria-label"> & {
|
||||
event: NostrEvent;
|
||||
@ -56,7 +56,7 @@ export const NoteRelays = memo(({ event, ...props }: NoteRelaysProps) => {
|
||||
action.subscribe({
|
||||
next: (result) => {
|
||||
if (result.status) {
|
||||
handleEventFromRelay(relayPool.requestRelay(result.url, false), event);
|
||||
handleEventFromRelay(relayPoolService.requestRelay(result.url, false), event);
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Badge, useForceUpdate } from "@chakra-ui/react";
|
||||
import { useInterval } from "react-use";
|
||||
import { Relay, relayPool } from "../services/relays";
|
||||
import { Relay } from "../classes/relay";
|
||||
import relayPoolService from "../services/relay-pool";
|
||||
|
||||
const getStatusText = (relay: Relay) => {
|
||||
if (relay.connecting) return "Connecting...";
|
||||
@ -20,7 +21,7 @@ const getStatusColor = (relay: Relay) => {
|
||||
export const RelayStatus = ({ url }: { url: string }) => {
|
||||
const update = useForceUpdate();
|
||||
|
||||
const relay = relayPool.requestRelay(url, false);
|
||||
const relay = relayPoolService.requestRelay(url, false);
|
||||
|
||||
useInterval(() => update(), 500);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import moment from "moment";
|
||||
import { getEventRelays } from "../services/event-relays";
|
||||
import { DraftNostrEvent, isETag, isPTag, NostrEvent, RTag } from "../types/nostr-event";
|
||||
import { RelayConfig, RelayMode } from "../services/relays/relay";
|
||||
import { RelayConfig, RelayMode } from "../classes/relay";
|
||||
|
||||
export function isReply(event: NostrEvent | DraftNostrEvent) {
|
||||
return !!event.tags.find((tag) => isETag(tag) && tag[3] !== "mention");
|
||||
|
8
src/hooks/use-client-relays copy.ts
Normal file
8
src/hooks/use-client-relays copy.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { useAsync } from "react-use";
|
||||
import relayInfoService from "../services/relay-info";
|
||||
|
||||
export function useRelayInfo(relay: string) {
|
||||
const { value: info, loading, error } = useAsync(() => relayInfoService.getInfo(relay));
|
||||
|
||||
return { info, loading, error };
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { unique } from "../helpers/array";
|
||||
import clientRelaysService from "../services/client-relays";
|
||||
import { RelayMode } from "../services/relays/relay";
|
||||
import { RelayMode } from "../classes/relay";
|
||||
import useSubject from "./use-subject";
|
||||
|
||||
export function useClientRelays(mode: RelayMode = RelayMode.READ) {
|
||||
|
@ -4,7 +4,7 @@ import { nostrPostAction } from "../classes/nostr-post-action";
|
||||
import { unique } from "../helpers/array";
|
||||
import { DraftNostrEvent, RTag } from "../types/nostr-event";
|
||||
import identity from "./identity";
|
||||
import { RelayConfig, RelayMode } from "./relays/relay";
|
||||
import { RelayConfig, RelayMode } from "../classes/relay";
|
||||
import userRelaysService from "./user-relays";
|
||||
|
||||
export type RelayDirectory = Record<string, { read: boolean; write: boolean }>;
|
||||
|
@ -34,10 +34,10 @@ const MIGRATIONS: MigrationFunction[] = [
|
||||
dnsIdentifiers.createIndex("domain", "domain", { unique: false });
|
||||
dnsIdentifiers.createIndex("updated", "updated", { unique: false });
|
||||
|
||||
const pubkeyRelayWeights = db.createObjectStore("pubkeyRelayWeights", { keyPath: "pubkey" });
|
||||
db.createObjectStore("pubkeyRelayWeights", { keyPath: "pubkey" });
|
||||
|
||||
// setup data
|
||||
const settings = db.createObjectStore("settings");
|
||||
db.createObjectStore("settings");
|
||||
db.createObjectStore("relayInfo");
|
||||
},
|
||||
];
|
||||
|
||||
@ -65,6 +65,7 @@ export async function clearCacheData() {
|
||||
}
|
||||
|
||||
export async function deleteDatabase() {
|
||||
db.close();
|
||||
await deleteDB(dbName);
|
||||
window.location.reload();
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DBSchema } from "idb";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { RelayConfig } from "../relays/relay";
|
||||
import { RelayInformationDocument } from "../relay-info";
|
||||
|
||||
export interface CustomSchema extends DBSchema {
|
||||
userMetadata: {
|
||||
@ -21,7 +21,7 @@ export interface CustomSchema extends DBSchema {
|
||||
};
|
||||
userRelays: {
|
||||
key: string;
|
||||
value: { pubkey: string; relays: {url: string, mode: number}[]; created_at: number };
|
||||
value: { pubkey: string; relays: { url: string; mode: number }[]; created_at: number };
|
||||
indexes: { created_at: number };
|
||||
};
|
||||
dnsIdentifiers: {
|
||||
@ -29,6 +29,7 @@ export interface CustomSchema extends DBSchema {
|
||||
value: { name: string; domain: string; pubkey: string; relays: string[]; updated: number };
|
||||
indexes: { name: string; domain: string; pubkey: string; updated: number };
|
||||
};
|
||||
relayInfo: { key: string; value: RelayInformationDocument };
|
||||
pubkeyRelayWeights: {
|
||||
key: string;
|
||||
value: { pubkey: string; relays: Record<string, number>; updated: number };
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { Relay } from "../classes/relay";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { Relay, relayPool } from "./relays";
|
||||
import relayPoolService from "./relay-pool";
|
||||
|
||||
const eventRelays = new Map<string, BehaviorSubject<string[]>>();
|
||||
|
||||
@ -21,7 +22,7 @@ export function handleEventFromRelay(relay: Relay, event: NostrEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
relayPool.onRelayCreated.subscribe((relay) => {
|
||||
relayPoolService.onRelayCreated.subscribe((relay) => {
|
||||
relay.onEvent.subscribe(({ body: event }) => {
|
||||
handleEventFromRelay(relay, event);
|
||||
});
|
||||
|
57
src/services/relay-info.ts
Normal file
57
src/services/relay-info.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import db from "./db";
|
||||
|
||||
export type RelayInformationDocument = {
|
||||
name: string;
|
||||
description: string;
|
||||
pubkey: string;
|
||||
contact: string;
|
||||
supported_nips: string;
|
||||
software: string;
|
||||
version: string;
|
||||
};
|
||||
export type DnsIdentity = {
|
||||
name: string;
|
||||
domain: string;
|
||||
pubkey: string;
|
||||
relays: string[];
|
||||
};
|
||||
|
||||
async function fetchInfo(relay: string) {
|
||||
const url = new URL(relay);
|
||||
url.protocol = url.protocol === "ws:" ? "http" : "https";
|
||||
|
||||
const infoDoc = await fetch(url, { headers: { Accept: "application/nostr+json" } }).then(
|
||||
(res) => res.json() as Promise<RelayInformationDocument>
|
||||
);
|
||||
|
||||
await db.put("relayInfo", infoDoc, relay);
|
||||
|
||||
return infoDoc;
|
||||
}
|
||||
|
||||
async function getInfo(relay: string) {
|
||||
const cached = await db.get("relayInfo", relay);
|
||||
if (cached) return cached;
|
||||
|
||||
// TODO: if it fails, maybe cache a failure message
|
||||
return fetchInfo(relay);
|
||||
}
|
||||
|
||||
const pending: Record<string, ReturnType<typeof getInfo> | undefined> = {};
|
||||
function dedupedGetIdentity(relay: string) {
|
||||
const request = pending[relay];
|
||||
if (request) return request;
|
||||
return (pending[relay] = getInfo(relay));
|
||||
}
|
||||
|
||||
export const relayInfoService = {
|
||||
fetchInfo,
|
||||
getInfo: dedupedGetIdentity,
|
||||
};
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-ignore
|
||||
window.relayInfoService = relayInfoService;
|
||||
}
|
||||
|
||||
export default relayInfoService;
|
@ -1,7 +1,7 @@
|
||||
import { Subject } from "rxjs";
|
||||
import { Relay } from "./relay";
|
||||
import { Relay } from "../classes/relay";
|
||||
|
||||
export class RelayPool {
|
||||
export class RelayPoolService {
|
||||
relays = new Map<string, Relay>();
|
||||
relayClaims = new Map<string, Set<any>>();
|
||||
onRelayCreated = new Subject<Relay>();
|
||||
@ -64,11 +64,11 @@ export class RelayPool {
|
||||
}
|
||||
}
|
||||
|
||||
const relayPool = new RelayPool();
|
||||
const relayPoolService = new RelayPoolService();
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-ignore
|
||||
window.relayPool = relayPool;
|
||||
window.relayPoolService = relayPoolService;
|
||||
}
|
||||
|
||||
export default relayPool;
|
||||
export default relayPoolService;
|
@ -1,4 +0,0 @@
|
||||
import relayPool, { RelayPool } from "./relay-pool";
|
||||
import { Relay } from "./relay";
|
||||
|
||||
export { relayPool, Relay, RelayPool };
|
@ -3,7 +3,7 @@ import { NostrSubscription } from "../classes/nostr-subscription";
|
||||
import { PubkeySubjectCache } from "../classes/pubkey-subject-cache";
|
||||
import { isRTag, NostrEvent } from "../types/nostr-event";
|
||||
import { NostrQuery } from "../types/nostr-query";
|
||||
import { RelayConfig } from "./relays/relay";
|
||||
import { RelayConfig } from "../classes/relay";
|
||||
import { parseRTag } from "../helpers/nostr-event";
|
||||
import clientRelaysService from "./client-relays";
|
||||
|
||||
|
@ -19,13 +19,16 @@ import { TrashIcon, UndoIcon } from "../../components/icons";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { RelayFavicon } from "../../components/relay-favicon";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
import { RelayConfig, RelayMode } from "../../services/relays/relay";
|
||||
import { RelayConfig, RelayMode } from "../../classes/relay";
|
||||
import { useList } from "react-use";
|
||||
import { RelayUrlInput } from "../../components/relay-url-input";
|
||||
import { useRelayInfo } from "../../hooks/use-client-relays copy";
|
||||
|
||||
export const RelaysView = () => {
|
||||
const relays = useSubject(clientRelaysService.relays);
|
||||
|
||||
const info = useRelayInfo("wss://nostr.wine");
|
||||
|
||||
const [pendingAdd, addActions] = useList<RelayConfig>([]);
|
||||
const [pendingRemove, removeActions] = useList<RelayConfig>([]);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user