Manual load more
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing

This commit is contained in:
Kieran 2023-07-23 23:19:26 +01:00
parent b1f93c9fd8
commit 63454728f8
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
18 changed files with 70 additions and 110 deletions

View File

@ -8,13 +8,11 @@ import Icon from "Icons/Icon";
import { dedupeByPubkey, findTag, tagFilterOfTextRepost } from "SnortUtils"; import { dedupeByPubkey, findTag, tagFilterOfTextRepost } from "SnortUtils";
import ProfileImage from "Element/ProfileImage"; import ProfileImage from "Element/ProfileImage";
import useTimelineFeed, { TimelineFeed, TimelineSubject } from "Feed/TimelineFeed"; import useTimelineFeed, { TimelineFeed, TimelineSubject } from "Feed/TimelineFeed";
import LoadMore from "Element/LoadMore";
import Zap from "Element/Zap"; import Zap from "Element/Zap";
import Note from "Element/Note"; import Note from "Element/Note";
import NoteReaction from "Element/NoteReaction"; import NoteReaction from "Element/NoteReaction";
import useModeration from "Hooks/useModeration"; import useModeration from "Hooks/useModeration";
import ProfilePreview from "Element/ProfilePreview"; import ProfilePreview from "Element/ProfilePreview";
import Skeleton from "Element/Skeleton";
import { UserCache } from "Cache"; import { UserCache } from "Cache";
export interface TimelineProps { export interface TimelineProps {
@ -142,11 +140,11 @@ const Timeline = (props: TimelineProps) => {
)} )}
{mainFeed.map(eventElement)} {mainFeed.map(eventElement)}
{(props.loadMore === undefined || props.loadMore === true) && ( {(props.loadMore === undefined || props.loadMore === true) && (
<LoadMore onLoadMore={() => feed.loadMore()} shouldLoadMore={!feed.loading}> <div className="flex f-center">
<Skeleton width="100%" height="120px" margin="0 0 16px 0" /> <button type="button" onClick={() => feed.loadMore()}>
<Skeleton width="100%" height="120px" margin="0 0 16px 0" /> <FormattedMessage defaultMessage="Load more" />
<Skeleton width="100%" height="120px" margin="0 0 16px 0" /> </button>
</LoadMore> </div>
)} )}
</> </>
); );

View File

@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo } from "react"; import { useCallback, useEffect, useMemo } from "react";
import { EventKind, u256, FlatNoteStore, RequestBuilder } from "@snort/system"; import { EventKind, FlatNoteStore, RequestBuilder } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react"; import { useRequestBuilder } from "@snort/system-react";
import { unixNow, unwrap, tagFilterOfTextRepost } from "SnortUtils"; import { unixNow, unwrap, tagFilterOfTextRepost } from "SnortUtils";

View File

@ -9,7 +9,7 @@
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"scripts": { "scripts": {
"build": "rm -rf dist && tsc", "build": "rm -rf dist && tsc",
"test": "jest" "test": "jest --runInBand"
}, },
"files": [ "files": [
"src", "src",

View File

@ -61,63 +61,26 @@ export function splitByWriteRelays(cache: RelayCache, filter: ReqFilter): Array<
]; ];
} }
const allRelays = unwrap(authors).map(a => { const topRelays = pickTopRelays(cache, unwrap(authors), PickNRelays);
return { const pickedRelays = dedupe(topRelays.flatMap(a => a.relays));
key: a,
relays: cache
.getFromCache(a)
?.relays?.filter(a => a.settings.write)
.sort(() => (Math.random() < 0.5 ? 1 : -1)),
};
});
const missing = allRelays.filter(a => a.relays === undefined || a.relays.length === 0); const picked = pickedRelays.map(a => {
const hasRelays = allRelays.filter(a => a.relays !== undefined && a.relays.length > 0); const keysOnPickedRelay = dedupe(topRelays.filter(b => b.relays.includes(a)).map(b => b.key));
const relayUserMap = hasRelays.reduce((acc, v) => {
for (const r of unwrap(v.relays)) {
if (!acc.has(r.url)) {
acc.set(r.url, new Set([v.key]));
} else {
unwrap(acc.get(r.url)).add(v.key);
}
}
return acc;
}, new Map<string, Set<string>>());
// selection algo will just pick relays with the most users
const topRelays = [...relayUserMap.entries()].sort(([, v], [, v1]) => v1.size - v.size);
// <relay, key[]> - count keys per relay
// <key, relay[]> - pick n top relays
// <relay, key[]> - map keys per relay (for subscription filter)
const userPickedRelays = unwrap(authors).map(k => {
// pick top 3 relays for this key
const relaysForKey = topRelays
.filter(([, v]) => v.has(k))
.slice(0, PickNRelays)
.map(([k]) => k);
return { k, relaysForKey };
});
const pickedRelays = new Set(userPickedRelays.map(a => a.relaysForKey).flat());
const picked = [...pickedRelays].map(a => {
const keysOnPickedRelay = new Set(userPickedRelays.filter(b => b.relaysForKey.includes(a)).map(b => b.k));
return { return {
relay: a, relay: a,
filter: { filter: {
...filter, ...filter,
authors: [...keysOnPickedRelay], authors: keysOnPickedRelay,
}, },
} as RelayTaggedFilter; } as RelayTaggedFilter;
}); });
if (missing.length > 0) { const noRelays = dedupe(topRelays.filter(a => a.relays.length === 0).map(a => a.key));
if (noRelays.length > 0) {
picked.push({ picked.push({
relay: "", relay: "",
filter: { filter: {
...filter, ...filter,
authors: missing.map(a => a.key), authors: noRelays,
}, },
}); });
} }
@ -142,24 +105,20 @@ export function splitFlatByWriteRelays(cache: RelayCache, input: Array<FlatReqFi
const pickedRelays = dedupe(topRelays.flatMap(a => a.relays)); const pickedRelays = dedupe(topRelays.flatMap(a => a.relays));
const picked = pickedRelays.map(a => { const picked = pickedRelays.map(a => {
const keysOnPickedRelay = new Set(userPickedRelays.filter(b => b.relaysForKey.includes(a)).map(b => b.k)); const authorsOnRelay = new Set(topRelays.filter(v => v.relays.includes(a)).map(v => v.key));
return { return {
relay: a, relay: a,
filter: { filters: input.filter(v => v.authors && authorsOnRelay.has(v.authors)),
...filter, } as RelayTaggedFlatFilters;
authors: [...keysOnPickedRelay],
},
} as RelayTaggedFilter;
}); });
if (missing.length > 0) { const noRelays = new Set(topRelays.filter(v => v.relays.length === 0).map(v => v.key));
if (noRelays.size > 0) {
picked.push({ picked.push({
relay: "", relay: "",
filter: { filters: input.filter(v => !v.authors || noRelays.has(v.authors)),
...filter, } as RelayTaggedFlatFilters);
authors: missing.map(a => a.key),
},
});
} }
debug("GOSSIP")("Picked %o", picked); debug("GOSSIP")("Picked %o", picked);
return picked; return picked;
} }

View File

@ -126,7 +126,7 @@ export class ProfileLoaderService {
const empty = couldNotFetch.map(a => const empty = couldNotFetch.map(a =>
this.#cache.update({ this.#cache.update({
pubkey: a, pubkey: a,
loaded: unixNowMs() - ProfileCacheExpire + 5_000, // expire in 5s loaded: unixNowMs() - ProfileCacheExpire + 30_000, // expire in 30s
created: 69, created: 69,
} as MetadataCache) } as MetadataCache)
); );

View File

@ -250,7 +250,7 @@ export class Query implements QueryBase {
#onProgress() { #onProgress() {
const isFinished = this.progress === 1; const isFinished = this.progress === 1;
if (this.feed.loading !== isFinished) { if (this.feed.loading !== isFinished) {
this.#log("%s loading=%s, progress=%d", this.id, this.feed.loading, this.progress); this.#log("%s loading=%s, progress=%d, traces=%O", this.id, this.feed.loading, this.progress, this.#tracing);
this.feed.loading = isFinished; this.feed.loading = isFinished;
} }
} }

View File

@ -4,8 +4,8 @@ import { appendDedupe, sanitizeRelayUrl, unixNowMs } from "@snort/shared";
import { ReqFilter, u256, HexKey, EventKind } from "."; import { ReqFilter, u256, HexKey, EventKind } from ".";
import { diffFilters } from "./request-splitter"; import { diffFilters } from "./request-splitter";
import { RelayCache, splitAllByWriteRelays, splitByWriteRelays } from "./gossip-model"; import { RelayCache, splitByWriteRelays, splitFlatByWriteRelays } from "./gossip-model";
import { mergeSimilar } from "./request-merger"; import { flatMerge, mergeSimilar } from "./request-merger";
import { FlatReqFilter, expandFilter } from "./request-expander"; import { FlatReqFilter, expandFilter } from "./request-expander";
/** /**
@ -111,10 +111,10 @@ export class RequestBuilder {
const ts = unixNowMs() - start; const ts = unixNowMs() - start;
this.#log("buildDiff %s %d ms", this.id, ts); this.#log("buildDiff %s %d ms", this.id, ts);
if (diff.changed) { if (diff.changed) {
return splitAllByWriteRelays(relays, diff.added).map(a => { return splitFlatByWriteRelays(relays, diff.added).map(a => {
return { return {
strategy: RequestStrategy.AuthorsRelays, strategy: RequestStrategy.AuthorsRelays,
filters: a.filters, filters: flatMerge(a.filters),
relay: a.relay, relay: a.relay,
}; };
}); });

View File

@ -1,13 +1,10 @@
import { flatFilterEq } from "./utils"; import { flatFilterEq } from "./utils";
import { FlatReqFilter } from "./request-expander"; import { FlatReqFilter } from "./request-expander";
import { flatMerge } from "./request-merger";
export function diffFilters(prev: Array<FlatReqFilter>, next: Array<FlatReqFilter>, calcRemoved?: boolean) { export function diffFilters(prev: Array<FlatReqFilter>, next: Array<FlatReqFilter>, calcRemoved?: boolean) {
const added = []; const added = [];
const removed = []; const removed = [];
prev = [...prev];
next = [...next];
for (const n of next) { for (const n of next) {
const px = prev.findIndex(a => flatFilterEq(a, n)); const px = prev.findIndex(a => flatFilterEq(a, n));
if (px !== -1) { if (px !== -1) {

View File

@ -1,9 +1,9 @@
import { Connection } from "../src"; import { Connection } from "../src";
import { describe, expect } from "@jest/globals"; import { describe, expect } from "@jest/globals";
import { Query } from "../src/Query"; import { Query } from "../src/query";
import { getRandomValues } from "crypto"; import { getRandomValues } from "crypto";
import { FlatNoteStore } from "../src/NoteCollection"; import { FlatNoteStore } from "../src/note-collection";
import { RequestStrategy } from "../src/RequestBuilder"; import { RequestStrategy } from "../src/request-builder";
window.crypto = {} as any; window.crypto = {} as any;
window.crypto.getRandomValues = getRandomValues as any; window.crypto.getRandomValues = getRandomValues as any;

View File

@ -1,4 +1,4 @@
import { EventExt } from "../src/EventExt"; import { EventExt } from "../src/event-ext";
describe("NIP-10", () => { describe("NIP-10", () => {
it("should extract thread", () => { it("should extract thread", () => {

View File

@ -1,4 +1,4 @@
import { splitAllByWriteRelays } from "../src/GossipModel"; import { splitAllByWriteRelays } from "../src/gossip-model";
describe("GossipModel", () => { describe("GossipModel", () => {
it("should not output empty", () => { it("should not output empty", () => {

View File

@ -1,6 +1,6 @@
import { TaggedRawEvent } from "../src/Nostr"; import { TaggedRawEvent } from "../src/nostr";
import { describe, expect } from "@jest/globals"; import { describe, expect } from "@jest/globals";
import { FlatNoteStore, ReplaceableNoteStore } from "../src/NoteCollection"; import { FlatNoteStore, ReplaceableNoteStore } from "../src/note-collection";
describe("NoteStore", () => { describe("NoteStore", () => {
describe("flat", () => { describe("flat", () => {

View File

@ -1,7 +1,7 @@
import { RelayCache } from "../src/GossipModel"; import { RelayCache } from "../src/gossip-model";
import { RequestBuilder, RequestStrategy } from "../src/RequestBuilder"; import { RequestBuilder, RequestStrategy } from "../src/request-builder";
import { describe, expect } from "@jest/globals"; import { describe, expect } from "@jest/globals";
import { expandFilter } from "../src/RequestExpander"; import { expandFilter } from "../src/request-expander";
import { bytesToHex } from "@noble/curves/abstract/utils"; import { bytesToHex } from "@noble/curves/abstract/utils";
import { unixNow, unixNowMs } from "@snort/shared"; import { unixNow, unixNowMs } from "@snort/shared";

View File

@ -1,4 +1,4 @@
import { expandFilter } from "../src/RequestExpander"; import { expandFilter } from "../src/request-expander";
describe("RequestExpander", () => { describe("RequestExpander", () => {
test("expand filter", () => { test("expand filter", () => {

View File

@ -1,4 +1,4 @@
import { eventMatchesFilter } from "../src/RequestMatcher"; import { eventMatchesFilter } from "../src/request-matcher";
describe("RequestMatcher", () => { describe("RequestMatcher", () => {
it("should match simple filter", () => { it("should match simple filter", () => {

View File

@ -1,6 +1,6 @@
import { ReqFilter } from "../src"; import { ReqFilter } from "../src";
import { canMergeFilters, filterIncludes, flatMerge, mergeSimilar, simpleMerge } from "../src/RequestMerger"; import { canMergeFilters, filterIncludes, flatMerge, mergeSimilar, simpleMerge } from "../src/request-merger";
import { FlatReqFilter, expandFilter } from "../src/RequestExpander"; import { FlatReqFilter, expandFilter } from "../src/request-expander";
describe("RequestMerger", () => { describe("RequestMerger", () => {
it("should simple merge authors", () => { it("should simple merge authors", () => {

View File

@ -1,15 +1,15 @@
import { ReqFilter } from "../src"; import { ReqFilter } from "../src";
import { describe, expect } from "@jest/globals"; import { describe, expect } from "@jest/globals";
import { diffFilters } from "../src/RequestSplitter"; import { diffFilters } from "../src/request-splitter";
import { expandFilter } from "../src/RequestExpander"; import { expandFilter } from "../src/request-expander";
describe("RequestSplitter", () => { describe("RequestSplitter", () => {
test("single filter add value", () => { test("single filter add value", () => {
const a: Array<ReqFilter> = [{ kinds: [0], authors: ["a"] }]; const a: Array<ReqFilter> = [{ kinds: [0], authors: ["a"] }];
const b: Array<ReqFilter> = [{ kinds: [0], authors: ["a", "b"] }]; const b: Array<ReqFilter> = [{ kinds: [0], authors: ["a", "b"] }];
const diff = diffFilters(a.flatMap(expandFilter), b.flatMap(expandFilter), true); const diff = diffFilters(a.flatMap(expandFilter), b.flatMap(expandFilter), true);
expect(diff).toEqual({ expect(diff).toMatchObject({
added: [{ kinds: [0], authors: ["b"] }], added: [{ kinds: 0, authors: "b" }],
removed: [], removed: [],
changed: true, changed: true,
}); });
@ -18,9 +18,9 @@ describe("RequestSplitter", () => {
const a: Array<ReqFilter> = [{ kinds: [0], authors: ["a"] }]; const a: Array<ReqFilter> = [{ kinds: [0], authors: ["a"] }];
const b: Array<ReqFilter> = [{ kinds: [0], authors: ["b"] }]; const b: Array<ReqFilter> = [{ kinds: [0], authors: ["b"] }];
const diff = diffFilters(a.flatMap(expandFilter), b.flatMap(expandFilter), true); const diff = diffFilters(a.flatMap(expandFilter), b.flatMap(expandFilter), true);
expect(diff).toEqual({ expect(diff).toMatchObject({
added: [{ kinds: [0], authors: ["b"] }], added: [{ kinds: 0, authors: "b" }],
removed: [{ kinds: [0], authors: ["a"] }], removed: [{ kinds: 0, authors: "a" }],
changed: true, changed: true,
}); });
}); });
@ -28,9 +28,12 @@ describe("RequestSplitter", () => {
const a: Array<ReqFilter> = [{ kinds: [0], authors: ["a"], since: 100 }]; const a: Array<ReqFilter> = [{ kinds: [0], authors: ["a"], since: 100 }];
const b: Array<ReqFilter> = [{ kinds: [0], authors: ["a", "b"], since: 101 }]; const b: Array<ReqFilter> = [{ kinds: [0], authors: ["a", "b"], since: 101 }];
const diff = diffFilters(a.flatMap(expandFilter), b.flatMap(expandFilter), true); const diff = diffFilters(a.flatMap(expandFilter), b.flatMap(expandFilter), true);
expect(diff).toEqual({ expect(diff).toMatchObject({
added: [{ kinds: [0], authors: ["a", "b"], since: 101 }], added: [
removed: [{ kinds: [0], authors: ["a"], since: 100 }], { kinds: 0, authors: "a", since: 101 },
{ kinds: 0, authors: "b", since: 101 },
],
removed: [{ kinds: 0, authors: "a", since: 100 }],
changed: true, changed: true,
}); });
}); });
@ -44,10 +47,10 @@ describe("RequestSplitter", () => {
{ kinds: [69], authors: ["a", "c"] }, { kinds: [69], authors: ["a", "c"] },
]; ];
const diff = diffFilters(a.flatMap(expandFilter), b.flatMap(expandFilter), true); const diff = diffFilters(a.flatMap(expandFilter), b.flatMap(expandFilter), true);
expect(diff).toEqual({ expect(diff).toMatchObject({
added: [ added: [
{ kinds: [0], authors: ["b"] }, { kinds: 0, authors: "b" },
{ kinds: [69], authors: ["c"] }, { kinds: 69, authors: "c" },
], ],
removed: [], removed: [],
changed: true, changed: true,
@ -63,12 +66,15 @@ describe("RequestSplitter", () => {
{ kinds: [69], authors: ["c"] }, { kinds: [69], authors: ["c"] },
]; ];
const diff = diffFilters(a.flatMap(expandFilter), b.flatMap(expandFilter), true); const diff = diffFilters(a.flatMap(expandFilter), b.flatMap(expandFilter), true);
expect(diff).toEqual({ expect(diff).toMatchObject({
added: [ added: [
{ kinds: [0], authors: ["b"] }, { kinds: 0, authors: "b" },
{ kinds: [69], authors: ["c"] }, { kinds: 69, authors: "c" },
],
removed: [
{ kinds: 0, authors: "a" },
{ kinds: 69, authors: "a" },
], ],
removed: [{ kinds: [0, 69], authors: ["a"] }],
changed: true, changed: true,
}); });
}); });
@ -79,8 +85,8 @@ describe("RequestSplitter", () => {
{ kinds: [69], authors: ["c"] }, { kinds: [69], authors: ["c"] },
]; ];
const diff = diffFilters(a.flatMap(expandFilter), b.flatMap(expandFilter), true); const diff = diffFilters(a.flatMap(expandFilter), b.flatMap(expandFilter), true);
expect(diff).toEqual({ expect(diff).toMatchObject({
added: [{ kinds: [69], authors: ["c"] }], added: [{ kinds: 69, authors: "c" }],
removed: [], removed: [],
changed: true, changed: true,
}); });

View File

@ -1,5 +1,5 @@
import { NostrPrefix } from "../src/Links"; import { NostrPrefix } from "../src/links";
import { parseNostrLink, tryParseNostrLink } from "../src/NostrLink"; import { parseNostrLink, tryParseNostrLink } from "../src/nostr-link";
describe("tryParseNostrLink", () => { describe("tryParseNostrLink", () => {
it("is a valid nostr link", () => { it("is a valid nostr link", () => {