use nanoid for request and subscription ids

fix bug with useSubject hook
This commit is contained in:
hzrd149
2023-08-24 18:05:47 -05:00
parent 8a0934f096
commit 1740c813a9
13 changed files with 41 additions and 185 deletions

View File

@@ -27,6 +27,7 @@
"leaflet": "^1.9.4",
"leaflet.locatecontrol": "^0.79.0",
"light-bolt11-decoder": "^3.0.0",
"nanoid": "^4.0.2",
"ngeohash": "^0.6.3",
"noble-secp256k1": "^1.2.14",
"nostr-tools": "^1.14.0",

View File

@@ -1,11 +1,10 @@
import { nanoid } from "nanoid";
import { Subject } from "./subject";
import { NostrEvent } from "../types/nostr-event";
import { NostrOutgoingMessage, NostrRequestFilter } from "../types/nostr-query";
import { IncomingEvent, Relay } from "./relay";
import relayPoolService from "../services/relay-pool";
let lastId = 0;
export class NostrMultiSubscription {
static INIT = "initial";
static OPEN = "open";
@@ -21,7 +20,7 @@ export class NostrMultiSubscription {
seenEvents = new Set<string>();
constructor(relayUrls: string[], query?: NostrRequestFilter, name?: string) {
this.id = String(name || lastId++);
this.id = nanoid();
this.query = query;
this.name = name;
this.relayUrls = relayUrls;

View File

@@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { isReplaceable } from "../helpers/nostr/events";
import { addToLog } from "../services/publish-log";
import relayPoolService from "../services/relay-pool";
@@ -7,10 +8,8 @@ import createDefer from "./deferred";
import { IncomingCommandResult, Relay } from "./relay";
import Subject, { PersistentSubject } from "./subject";
let lastId = 0;
export default class NostrPublishAction {
id = lastId++;
id = nanoid();
label: string;
relays: string[];
event: NostrEvent;

View File

@@ -1,3 +1,4 @@
import { nanoid } from "nanoid";
import { NostrEvent } from "../types/nostr-event";
import { NostrRequestFilter } from "../types/nostr-query";
import relayPoolService from "../services/relay-pool";
@@ -5,8 +6,6 @@ import { IncomingEOSE, IncomingEvent, Relay } from "./relay";
import Subject from "./subject";
import createDefer from "./deferred";
let lastId = 0;
const REQUEST_DEFAULT_TIMEOUT = 1000 * 5;
export class NostrRequest {
static IDLE = "idle";
@@ -21,8 +20,8 @@ export class NostrRequest {
onComplete = createDefer<void>();
seenEvents = new Set<string>();
constructor(relayUrls: string[], timeout?: number, name?: string) {
this.id = name || `request-${lastId++}`;
constructor(relayUrls: string[], timeout?: number) {
this.id = nanoid();
this.relays = new Set(relayUrls.map((url) => relayPoolService.requestRelay(url)));
for (const relay of this.relays) {

View File

@@ -3,8 +3,7 @@ import { NostrOutgoingMessage, NostrRequestFilter } from "../types/nostr-query";
import { IncomingEOSE, Relay } from "./relay";
import relayPoolService from "../services/relay-pool";
import { Subject } from "./subject";
let lastId = 10000;
import { nanoid } from "nanoid";
export class NostrSubscription {
static INIT = "initial";
@@ -20,7 +19,7 @@ export class NostrSubscription {
onEOSE = new Subject<IncomingEOSE>();
constructor(relayUrl: string, query?: NostrRequestFilter, name?: string) {
this.id = String(name || lastId++);
this.id = nanoid();
this.query = query;
this.name = name;

View File

@@ -26,7 +26,6 @@ class RelayTimelineLoader {
query: NostrRequestFilter;
blockSize = BLOCK_SIZE;
private name?: string;
private requestId = 0;
private log: Debugger;
loading = false;
@@ -41,7 +40,7 @@ class RelayTimelineLoader {
this.query = query;
this.name = name;
this.log = log || logger.extend(name);
this.log = log || logger.extend(this.name);
this.events = new EventStore(relay);
}
@@ -53,7 +52,7 @@ class RelayTimelineLoader {
query = addToQuery(query, { until: oldestEvent.created_at - 1 });
}
const request = new NostrRequest([this.relay], undefined, this.name + "-" + this.requestId++);
const request = new NostrRequest([this.relay], 20 * 1000);
let gotEvents = 0;
request.onEvent.subscribe((e) => {
@@ -93,7 +92,7 @@ export class TimelineLoader {
loadNextBlockBuffer = 2;
eventFilter?: (event: NostrEvent) => boolean;
private name: string;
name: string;
private log: Debugger;
private subscription: NostrMultiSubscription;
@@ -166,6 +165,7 @@ export class TimelineLoader {
this.removeLoaders();
this.log("set query", query);
this.query = query;
this.events.clear();
this.timeline.next([]);

View File

@@ -1,40 +1,14 @@
import { PropsWithChildren, createContext, useContext, useMemo, useState } from "react";
import { nip19 } from "nostr-tools";
import { Kind } from "nostr-tools";
import { useCurrentAccount } from "../../hooks/use-current-account";
import useUserContactList from "../../hooks/use-user-contact-list";
import { getPubkeysFromList } from "../../helpers/nostr/lists";
import useReplaceableEvent from "../../hooks/use-replaceable-event";
export type ListIdentifier = "following" | "global" | string;
export function useParsedNaddr(naddr?: string) {
if (!naddr) return;
try {
const parsed = nip19.decode(naddr);
if (parsed.type === "naddr") {
return parsed.data;
}
} catch (e) {}
}
export function useListPeople(list: ListIdentifier) {
const account = useCurrentAccount();
const contacts = useUserContactList(account?.pubkey);
const listEvent = useReplaceableEvent(list.includes(":") ? list : undefined);
if (list === "following") return contacts ? getPubkeysFromList(contacts) : [];
if (listEvent) {
return getPubkeysFromList(listEvent);
}
return [];
}
export type PeopleListContextType = {
list: string;
people: { pubkey: string; relay?: string }[];
setList: (list: string) => void;
list?: string;
people: { pubkey: string; relay?: string }[] | undefined;
setList: (list: string | undefined) => void;
};
const PeopleListContext = createContext<PeopleListContextType>({ list: "following", setList: () => {}, people: [] });
@@ -44,16 +18,17 @@ export function usePeopleListContext() {
export default function PeopleListProvider({ children }: PropsWithChildren) {
const account = useCurrentAccount();
const [list, setList] = useState(account ? "following" : "global");
const [listCord, setList] = useState(account ? `${Kind.Contacts}:${account.pubkey}` : undefined);
const listEvent = useReplaceableEvent(listCord);
const people = useListPeople(list);
const people = listEvent && getPubkeysFromList(listEvent);
const context = useMemo(
() => ({
people,
list,
list: listCord,
setList,
}),
[list, setList],
[listCord, setList, people],
);
return <PeopleListContext.Provider value={context}>{children}</PeopleListContext.Provider>;

View File

@@ -13,7 +13,9 @@ function UserListOptions() {
return (
<>
{lists.map((list) => (
<option value={getEventCoordinate(list)}>{getListName(list)}</option>
<option key={getEventCoordinate(list)} value={getEventCoordinate(list)}>
{getListName(list)}
</option>
))}
</>
);
@@ -32,7 +34,7 @@ export default function PeopleListSelection({
<Select
value={list}
onChange={(e) => {
setList(e.target.value);
setList(e.target.value === "global" ? undefined : e.target.value);
}}
{...props}
>

View File

@@ -5,7 +5,7 @@ function useSubject<Value extends unknown>(subject: PersistentSubject<Value>): V
function useSubject<Value extends unknown>(subject?: PersistentSubject<Value>): Value | undefined;
function useSubject<Value extends unknown>(subject?: Subject<Value>): Value | undefined;
function useSubject<Value extends unknown>(subject?: Subject<Value>) {
const [value, setValue] = useState(subject?.value);
const [_, setValue] = useState(subject?.value);
useEffect(() => {
const handler = (value: Value) => setValue(value);
setValue(subject?.value);
@@ -16,7 +16,7 @@ function useSubject<Value extends unknown>(subject?: Subject<Value>) {
};
}, [subject, setValue]);
return value;
return subject?.value;
}
export default useSubject;

View File

@@ -1,17 +1,12 @@
import { truncatedId } from "../helpers/nostr/events";
import { NOTE_LIST_KIND, PEOPLE_LIST_KIND } from "../helpers/nostr/lists";
import { useReadRelayUrls } from "./use-client-relays";
import { useCurrentAccount } from "./use-current-account";
import useSubject from "./use-subject";
import useTimelineLoader from "./use-timeline-loader";
export default function useUserLists(pubkey: string, additionalRelays: string[] = []) {
const account = useCurrentAccount();
if (!account) throw new Error("No Account");
const readRelays = useReadRelayUrls(additionalRelays);
const timeline = useTimelineLoader(truncatedId(account.pubkey) + "-lists", readRelays, {
authors: [account.pubkey],
const timeline = useTimelineLoader(`${pubkey}-lists`, readRelays, {
authors: [pubkey],
kinds: [PEOPLE_LIST_KIND, NOTE_LIST_KIND],
});

View File

@@ -1,117 +0,0 @@
import { nip19 } from "nostr-tools";
import { NostrRequest } from "../classes/nostr-request";
import { PersistentSubject } from "../classes/subject";
import { NostrEvent, isPTag } from "../types/nostr-event";
import { getEventRelays } from "./event-relays";
import relayScoreboardService from "./relay-scoreboard";
import { getEventCoordinate } from "../helpers/nostr/events";
import { draftAddPerson, draftRemovePerson, getListName } from "../helpers/nostr/lists";
import replaceableEventLoaderService from "./replaceable-event-requester";
/** @deprecated */
export class List {
event: NostrEvent;
cord: string;
people = new PersistentSubject<{ pubkey: string; relay?: string }[]>([]);
get author() {
return this.event.pubkey;
}
get name() {
return getListName(this.event)!;
}
getAddress() {
// pick fastest for event
const relays = relayScoreboardService.getRankedRelays(getEventRelays(this.event.id).value).slice(0, 1);
return nip19.naddrEncode({
pubkey: this.event.pubkey,
identifier: this.name,
relays,
kind: this.event.kind,
});
}
constructor(event: NostrEvent) {
this.event = event;
this.cord = getEventCoordinate(event);
this.updatePeople();
}
private updatePeople() {
const people = this.event.tags.filter(isPTag).map((p) => ({ pubkey: p[1], relay: p[2] }));
this.people.next(people);
}
handleEvent(event: NostrEvent) {
if (event.created_at > this.event.created_at) {
this.event = event;
this.updatePeople();
}
}
draftAddPerson(pubkey: string, relay?: string) {
return draftAddPerson(this.event, pubkey, relay);
}
draftRemovePerson(pubkey: string) {
return draftRemovePerson(this.event, pubkey);
}
}
class ListsService {
private lists = new Map<string, List>();
private pubkeyLists = new Map<string, PersistentSubject<Record<string, List>>>();
private fetchingPubkeys = new Set();
fetchListsForPubkey(pubkey: string, relays: string[]) {
if (this.fetchingPubkeys.has(pubkey)) return this.pubkeyLists.get(pubkey)!;
this.fetchingPubkeys.add(pubkey);
if (!this.pubkeyLists.has(pubkey)) {
this.pubkeyLists.set(pubkey, new PersistentSubject<Record<string, List>>({}));
}
let subject = this.pubkeyLists.get(pubkey)!;
const request = new NostrRequest(relays);
request.onEvent.subscribe((event) => {
replaceableEventLoaderService.handleEvent(event);
const listName = getListName(event);
if (listName && event.kind === 30000) {
if (subject.value[listName]) {
subject.value[listName].handleEvent(event);
} else {
const list = new List(event);
this.lists.set(event.id, list);
subject.next({ ...subject.value, [listName]: list });
}
}
});
request.start({ kinds: [30000], authors: [pubkey] });
return subject;
}
requestUserLists(pubkey: string, relays: string[], alwaysFetch = false) {
if (!this.pubkeyLists.has(pubkey) || alwaysFetch) {
return this.fetchListsForPubkey(pubkey, relays);
}
return this.pubkeyLists.get(pubkey)!;
}
getListsForPubkey(pubkey: string) {
return this.pubkeyLists.get(pubkey);
}
}
const listsService = new ListsService();
if (import.meta.env.DEV) {
//@ts-ignore
window.listsService = listsService;
}
export default listsService;

View File

@@ -1,11 +1,11 @@
import { useCallback, useMemo, useState } from "react";
import { useCallback, useState } from "react";
import { Code, Flex, Select, SimpleGrid } from "@chakra-ui/react";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import IntersectionObserverProvider from "../../providers/intersection-observer";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import useSubject from "../../hooks/use-subject";
import StreamCard from "./components/stream-card";
import { ParsedStream, STREAM_KIND, parseStreamEvent } from "../../helpers/nostr/stream";
import { STREAM_KIND, parseStreamEvent } from "../../helpers/nostr/stream";
import { NostrEvent } from "../../types/nostr-event";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import RelaySelectionProvider, { useRelaySelectionRelays } from "../../providers/relay-selection-provider";
@@ -32,15 +32,14 @@ function StreamsPage() {
const { people, list } = usePeopleListContext();
const query =
people.length > 0
people && people.length > 0
? [
{ authors: people.map((p) => p.pubkey), kinds: [STREAM_KIND] },
{ "#p": people.map((p) => p.pubkey), kinds: [STREAM_KIND] },
]
: { kinds: [STREAM_KIND] };
// TODO: put the list id into the timeline key so it refreshes (probably have to hash the list id since its >64)
const timeline = useTimelineLoader(`streams`, relays, query, { eventFilter });
const timeline = useTimelineLoader(`${list}-streams`, relays, query, { eventFilter });
useRelaysChanged(relays, () => timeline.reset());

View File

@@ -5039,6 +5039,11 @@ nanoid@^3.3.6:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
nanoid@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==
ngeohash@^0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/ngeohash/-/ngeohash-0.6.3.tgz#10b1e80be5488262ec95c56cf2dbb6c45fbdf245"