From 92c26ca609954bb1b77d6d5122c52f8da3bc3730 Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 22 Nov 2023 11:40:28 +0000 Subject: [PATCH 01/21] feat: write reply events to recipients relays --- packages/app/src/Feed/RelaysFeed.tsx | 5 +- packages/app/src/Feed/RelaysFeedFollows.tsx | 17 +---- packages/system/src/cache/index.ts | 1 + packages/system/src/const.ts | 5 ++ packages/system/src/index.ts | 5 +- packages/system/src/nostr-system.ts | 14 ++-- .../src/{gossip-model.ts => outbox-model.ts} | 76 +++++++++++++++++-- packages/system/src/request-builder.ts | 2 +- 8 files changed, 91 insertions(+), 34 deletions(-) rename packages/system/src/{gossip-model.ts => outbox-model.ts} (64%) diff --git a/packages/app/src/Feed/RelaysFeed.tsx b/packages/app/src/Feed/RelaysFeed.tsx index d5d929387..a973fe2d8 100644 --- a/packages/app/src/Feed/RelaysFeed.tsx +++ b/packages/app/src/Feed/RelaysFeed.tsx @@ -1,7 +1,6 @@ import { useMemo } from "react"; -import { HexKey, EventKind, RequestBuilder, ReplaceableNoteStore } from "@snort/system"; +import { HexKey, EventKind, RequestBuilder, ReplaceableNoteStore, parseRelayTags } from "@snort/system"; import { useRequestBuilder } from "@snort/system-react"; -import { parseRelayTag } from "./RelaysFeedFollows"; export default function useRelaysFeed(pubkey?: HexKey) { const sub = useMemo(() => { @@ -12,5 +11,5 @@ export default function useRelaysFeed(pubkey?: HexKey) { }, [pubkey]); const relays = useRequestBuilder(ReplaceableNoteStore, sub); - return relays.data?.tags.filter(a => a[0] === "r").map(parseRelayTag) ?? []; + return parseRelayTags(relays.data?.tags.filter(a => a[0] === "r") ?? []); } diff --git a/packages/app/src/Feed/RelaysFeedFollows.tsx b/packages/app/src/Feed/RelaysFeedFollows.tsx index 43812fe85..bc06e7f4d 100644 --- a/packages/app/src/Feed/RelaysFeedFollows.tsx +++ b/packages/app/src/Feed/RelaysFeedFollows.tsx @@ -1,9 +1,8 @@ import { useMemo } from "react"; -import { HexKey, FullRelaySettings, TaggedNostrEvent, EventKind, NoteCollection, RequestBuilder } from "@snort/system"; +import { HexKey, FullRelaySettings, TaggedNostrEvent, EventKind, NoteCollection, RequestBuilder, parseRelayTags } from "@snort/system"; import { useRequestBuilder } from "@snort/system-react"; import debug from "debug"; -import { sanitizeRelayUrl } from "@/SnortUtils"; import { UserRelays } from "@/Cache"; interface RelayList { @@ -26,7 +25,7 @@ export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array a.url !== undefined), + relays: parseRelayTags(ev.tags), }; }); } @@ -36,14 +35,4 @@ export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array { return mapFromRelays(notesRelays); }, [relays]); -} - -export function parseRelayTag(tag: Array) { - return { - url: sanitizeRelayUrl(tag[1]), - settings: { - read: tag[2] === "read" || tag[2] === undefined, - write: tag[2] === "write" || tag[2] === undefined, - }, - } as FullRelaySettings; -} +} \ No newline at end of file diff --git a/packages/system/src/cache/index.ts b/packages/system/src/cache/index.ts index 140acecd1..b9ec1881e 100644 --- a/packages/system/src/cache/index.ts +++ b/packages/system/src/cache/index.ts @@ -46,6 +46,7 @@ export interface UsersRelays { pubkey: string; created_at: number; relays: FullRelaySettings[]; + loaded: number; } export function mapEventToProfile(ev: NostrEvent) { diff --git a/packages/system/src/const.ts b/packages/system/src/const.ts index bafea4616..795345bd9 100644 --- a/packages/system/src/const.ts +++ b/packages/system/src/const.ts @@ -19,6 +19,11 @@ export const TagRefRegex = /(#\[\d+\])/gm; */ export const ProfileCacheExpire = 1_000 * 60 * 60 * 6; +/** + * How long before relay lists should be refreshed + */ +export const RelayListCacheExpire = 1_000 * 60 * 60 * 12; + /** * Extract file extensions regex */ diff --git a/packages/system/src/index.ts b/packages/system/src/index.ts index 44a4646d1..ffe812487 100644 --- a/packages/system/src/index.ts +++ b/packages/system/src/index.ts @@ -4,7 +4,7 @@ import { NoteStore, NoteStoreSnapshotData } from "./note-collection"; import { Query } from "./query"; import { NostrEvent, ReqFilter, TaggedNostrEvent } from "./nostr"; import { ProfileLoaderService } from "./profile-cache"; -import { RelayCache } from "./gossip-model"; +import { RelayCache } from "./outbox-model"; import { QueryOptimizer } from "./query-optimizer"; import { base64 } from "@scure/base"; @@ -31,6 +31,7 @@ export * from "./pow"; export * from "./pow-util"; export * from "./query-optimizer"; export * from "./encrypted"; +export * from "./outbox-model"; export * from "./impl/nip4"; export * from "./impl/nip44"; @@ -71,7 +72,7 @@ export interface SystemInterface { * @param req Request to send to relays * @param cb A callback which will fire every 100ms when new data is received */ - Fetch(req: RequestBuilder, cb?: (evs: Array) => void): Promise; + Fetch(req: RequestBuilder, cb?: (evs: Array) => void): Promise>; /** * Create a new permanent connection to a relay diff --git a/packages/system/src/nostr-system.ts b/packages/system/src/nostr-system.ts index 0d6526b8b..84119bc3f 100644 --- a/packages/system/src/nostr-system.ts +++ b/packages/system/src/nostr-system.ts @@ -22,7 +22,7 @@ import { EventExt, } from "."; import { EventsCache } from "./cache/events"; -import { RelayCache } from "./gossip-model"; +import { RelayCache, pickRelaysForReply } from "./outbox-model"; import { QueryOptimizer, DefaultQueryOptimizer } from "./query-optimizer"; import { trimFilters } from "./request-trim"; @@ -246,7 +246,7 @@ export class NostrSystem extends EventEmitter implements Syst Fetch(req: RequestBuilder, cb?: (evs: Array) => void) { const q = this.Query(NoteCollection, req); - return new Promise(resolve => { + return new Promise>(resolve => { let t: ReturnType | undefined; let tBuf: Array = []; const releaseOnEvent = cb @@ -267,7 +267,7 @@ export class NostrSystem extends EventEmitter implements Syst releaseOnEvent?.(); releaseFeedHook(); q.cancel(); - resolve(unwrap(q.feed.snapshot.data)); + resolve(unwrap((q.feed as NoteCollection).snapshot.data)); } }); }); @@ -382,8 +382,9 @@ export class NostrSystem extends EventEmitter implements Syst */ async BroadcastEvent(ev: NostrEvent, cb?: (rsp: OkResponse) => void) { const socks = [...this.#sockets.values()].filter(a => !a.Ephemeral && a.Settings.write); - const oks = await Promise.all( - socks.map(async s => { + const replyRelays = await pickRelaysForReply(ev, this); + const oks = await Promise.all([ + ...socks.map(async s => { try { const rsp = await s.SendAsync(ev); cb?.(rsp); @@ -393,7 +394,8 @@ export class NostrSystem extends EventEmitter implements Syst } return; }), - ); + ...replyRelays.filter(a => !socks.some(b => b.Address === a)).map(a => this.WriteOnceToRelay(a, ev)), + ]); return removeUndefined(oks); } diff --git a/packages/system/src/gossip-model.ts b/packages/system/src/outbox-model.ts similarity index 64% rename from packages/system/src/gossip-model.ts rename to packages/system/src/outbox-model.ts index caa057924..dd44e8bfb 100644 --- a/packages/system/src/gossip-model.ts +++ b/packages/system/src/outbox-model.ts @@ -1,7 +1,16 @@ -import { ReqFilter, UsersRelays } from "."; -import { dedupe, unwrap } from "@snort/shared"; +import { + EventKind, + FullRelaySettings, + NostrEvent, + ReqFilter, + RequestBuilder, + SystemInterface, + UsersRelays, +} from "."; +import { dedupe, sanitizeRelayUrl, unixNowMs, unwrap } from "@snort/shared"; import debug from "debug"; import { FlatReqFilter } from "./query-optimizer"; +import { RelayListCacheExpire } from "./const"; const PickNRelays = 2; @@ -20,8 +29,13 @@ export interface RelayTaggedFilters { filters: Array; } +const logger = debug("OutboxModel"); + export interface RelayCache { getFromCache(pubkey?: string): UsersRelays | undefined; + update(obj: UsersRelays): Promise<"new" | "updated" | "refresh" | "no_change">; + buffer(keys: Array): Promise>; + bulkSet(objs: Array): Promise; } export function splitAllByWriteRelays(cache: RelayCache, filters: Array) { @@ -61,7 +75,7 @@ export function splitByWriteRelays(cache: RelayCache, filter: ReqFilter): Array< ]; } - const topRelays = pickTopRelays(cache, unwrap(authors), PickNRelays); + const topRelays = pickTopRelays(cache, unwrap(authors), PickNRelays, "write"); const pickedRelays = dedupe(topRelays.flatMap(a => a.relays)); const picked = pickedRelays.map(a => { @@ -84,7 +98,7 @@ export function splitByWriteRelays(cache: RelayCache, filter: ReqFilter): Array< }, }); } - debug("GOSSIP")("Picked %O => %O", filter, picked); + logger("Picked %O => %O", filter, picked); return picked; } @@ -101,7 +115,7 @@ export function splitFlatByWriteRelays(cache: RelayCache, input: Array a.relays)); const picked = pickedRelays.map(a => { @@ -119,21 +133,21 @@ export function splitFlatByWriteRelays(cache: RelayCache, input: Array, n: number) { +function pickTopRelays(cache: RelayCache, authors: Array, n: number, type: "write" | "read") { // map of pubkey -> [write relays] const allRelays = authors.map(a => { return { key: a, relays: cache .getFromCache(a) - ?.relays?.filter(a => a.settings.write) + ?.relays?.filter(a => (type === "write" ? a.settings.write : a.settings.read)) .sort(() => (Math.random() < 0.5 ? 1 : -1)), }; }); @@ -178,3 +192,49 @@ function pickTopRelays(cache: RelayCache, authors: Array, n: number) { }), ); } + +/** + * Pick read relays for sending reply events + */ +export async function pickRelaysForReply(ev: NostrEvent, system: SystemInterface) { + const recipients = dedupe(ev.tags.filter(a => a[0] === "p").map(a => a[1])); + await updateRelayLists(recipients, system); + const relays = pickTopRelays(system.RelayCache, recipients, 2, "read"); + const ret = dedupe(relays.map(a => a.relays).flat()); + logger("Picked %O from authors %O", ret, recipients); + return ret; +} + +export function parseRelayTag(tag: Array) { + return { + url: sanitizeRelayUrl(tag[1]), + settings: { + read: tag[2] === "read" || tag[2] === undefined, + write: tag[2] === "write" || tag[2] === undefined, + }, + } as FullRelaySettings; +} + +export function parseRelayTags(tag: Array>) { + return tag.map(parseRelayTag).filter(a => a !== null); +} + +export async function updateRelayLists(authors: Array, system: SystemInterface) { + await system.RelayCache.buffer(authors); + const expire = unixNowMs() - RelayListCacheExpire; + const expired = authors.filter(a => (system.RelayCache.getFromCache(a)?.loaded ?? 0) < expire); + if (expired.length > 0) { + logger("Updating relays for authors: %O", expired); + const rb = new RequestBuilder("system-update-relays-for-outbox"); + rb.withFilter().authors(expired).kinds([EventKind.Relays]); + const relayLists = await system.Fetch(rb); + await system.RelayCache.bulkSet( + relayLists.map(a => ({ + relays: parseRelayTags(a.tags), + pubkey: a.pubkey, + created_at: a.created_at, + loaded: unixNowMs(), + })), + ); + } +} diff --git a/packages/system/src/request-builder.ts b/packages/system/src/request-builder.ts index d0310ee2d..fccdff280 100644 --- a/packages/system/src/request-builder.ts +++ b/packages/system/src/request-builder.ts @@ -5,7 +5,7 @@ import { appendDedupe, dedupe, sanitizeRelayUrl, unixNowMs, unwrap } from "@snor import EventKind from "./event-kind"; import { NostrLink, NostrPrefix, SystemInterface } from "."; import { ReqFilter, u256, HexKey } from "./nostr"; -import { RelayCache, splitByWriteRelays, splitFlatByWriteRelays } from "./gossip-model"; +import { RelayCache, splitByWriteRelays, splitFlatByWriteRelays } from "./outbox-model"; /** * Which strategy is used when building REQ filters From a80c330e5b96ca278b4c9ce0d4b0dc6f05a788cf Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 22 Nov 2023 11:41:32 +0000 Subject: [PATCH 02/21] chore: Update translations --- packages/app/src/Feed/RelaysFeedFollows.tsx | 12 ++++++++++-- packages/system/src/outbox-model.ts | 10 +--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/app/src/Feed/RelaysFeedFollows.tsx b/packages/app/src/Feed/RelaysFeedFollows.tsx index bc06e7f4d..6bea7675d 100644 --- a/packages/app/src/Feed/RelaysFeedFollows.tsx +++ b/packages/app/src/Feed/RelaysFeedFollows.tsx @@ -1,5 +1,13 @@ import { useMemo } from "react"; -import { HexKey, FullRelaySettings, TaggedNostrEvent, EventKind, NoteCollection, RequestBuilder, parseRelayTags } from "@snort/system"; +import { + HexKey, + FullRelaySettings, + TaggedNostrEvent, + EventKind, + NoteCollection, + RequestBuilder, + parseRelayTags, +} from "@snort/system"; import { useRequestBuilder } from "@snort/system-react"; import debug from "debug"; @@ -35,4 +43,4 @@ export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array { return mapFromRelays(notesRelays); }, [relays]); -} \ No newline at end of file +} diff --git a/packages/system/src/outbox-model.ts b/packages/system/src/outbox-model.ts index dd44e8bfb..83fd7fcba 100644 --- a/packages/system/src/outbox-model.ts +++ b/packages/system/src/outbox-model.ts @@ -1,12 +1,4 @@ -import { - EventKind, - FullRelaySettings, - NostrEvent, - ReqFilter, - RequestBuilder, - SystemInterface, - UsersRelays, -} from "."; +import { EventKind, FullRelaySettings, NostrEvent, ReqFilter, RequestBuilder, SystemInterface, UsersRelays } from "."; import { dedupe, sanitizeRelayUrl, unixNowMs, unwrap } from "@snort/shared"; import debug from "debug"; import { FlatReqFilter } from "./query-optimizer"; From a67263e5e1bef44801ef5c14f72bb837a3eeae39 Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 22 Nov 2023 13:58:11 +0000 Subject: [PATCH 03/21] feat: automated outbox model --- packages/app/src/Feed/LoginFeed.ts | 8 +- packages/app/src/Feed/RelaysFeedFollows.tsx | 46 ------ packages/system-react/src/context.tsx | 2 +- packages/system-react/src/useUserProfile.ts | 4 +- packages/system/src/background-loader.ts | 136 ++++++++++++++++ packages/system/src/cache/index.ts | 2 +- packages/system/src/cache/user-relays.ts | 2 +- packages/system/src/nostr-system.ts | 10 +- packages/system/src/outbox-model.ts | 38 ++++- packages/system/src/profile-cache.ts | 168 +++----------------- packages/system/src/relay-info.ts | 11 +- 11 files changed, 219 insertions(+), 208 deletions(-) delete mode 100644 packages/app/src/Feed/RelaysFeedFollows.tsx create mode 100644 packages/system/src/background-loader.ts diff --git a/packages/app/src/Feed/LoginFeed.ts b/packages/app/src/Feed/LoginFeed.ts index 291ea3954..92f027645 100644 --- a/packages/app/src/Feed/LoginFeed.ts +++ b/packages/app/src/Feed/LoginFeed.ts @@ -21,7 +21,6 @@ import { } from "@/Login"; import { SnortPubKey } from "@/Const"; import { SubscriptionEvent } from "@/Subscription"; -import useRelaysFeedFollows from "./RelaysFeedFollows"; import { FollowLists, FollowsFeed, GiftsCache, Notifications, UserRelays } from "@/Cache"; import { Nip28Chats, Nip4Chats } from "@/chat"; import { useRefreshFeedCache } from "@/Hooks/useRefreshFeedcache"; @@ -226,11 +225,6 @@ export default function useLoginFeed() { useEffect(() => { UserRelays.buffer(follows.item).catch(console.error); - system.ProfileLoader.TrackMetadata(follows.item); // always track follows profiles + system.ProfileLoader.TrackKeys(follows.item); // always track follows profiles }, [follows.item]); - - const fRelays = useRelaysFeedFollows(follows.item); - useEffect(() => { - UserRelays.bulkSet(fRelays).catch(console.error); - }, [fRelays]); } diff --git a/packages/app/src/Feed/RelaysFeedFollows.tsx b/packages/app/src/Feed/RelaysFeedFollows.tsx deleted file mode 100644 index 6bea7675d..000000000 --- a/packages/app/src/Feed/RelaysFeedFollows.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useMemo } from "react"; -import { - HexKey, - FullRelaySettings, - TaggedNostrEvent, - EventKind, - NoteCollection, - RequestBuilder, - parseRelayTags, -} from "@snort/system"; -import { useRequestBuilder } from "@snort/system-react"; -import debug from "debug"; - -import { UserRelays } from "@/Cache"; - -interface RelayList { - pubkey: string; - created_at: number; - relays: FullRelaySettings[]; -} - -export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array { - const sub = useMemo(() => { - const b = new RequestBuilder(`relays:follows`); - const since = UserRelays.newest(); - debug("LoginFeed")("Loading relay lists since %s", new Date(since * 1000).toISOString()); - b.withFilter().authors(pubkeys).kinds([EventKind.Relays]).since(since); - return b; - }, [pubkeys]); - - function mapFromRelays(notes: Array): Array { - return notes.map(ev => { - return { - pubkey: ev.pubkey, - created_at: ev.created_at, - relays: parseRelayTags(ev.tags), - }; - }); - } - - const relays = useRequestBuilder(NoteCollection, sub); - const notesRelays = relays.data?.filter(a => a.kind === EventKind.Relays) ?? []; - return useMemo(() => { - return mapFromRelays(notesRelays); - }, [relays]); -} diff --git a/packages/system-react/src/context.tsx b/packages/system-react/src/context.tsx index 4815ca3f3..cb2de0458 100644 --- a/packages/system-react/src/context.tsx +++ b/packages/system-react/src/context.tsx @@ -1,4 +1,4 @@ import { createContext } from "react"; import { NostrSystem, SystemInterface } from "@snort/system"; -export const SnortContext = createContext(new NostrSystem({})); +export const SnortContext = createContext({} as SystemInterface); diff --git a/packages/system-react/src/useUserProfile.ts b/packages/system-react/src/useUserProfile.ts index 5d7187619..95dd0a6c7 100644 --- a/packages/system-react/src/useUserProfile.ts +++ b/packages/system-react/src/useUserProfile.ts @@ -10,13 +10,13 @@ export function useUserProfile(pubKey?: HexKey): MetadataCache | undefined { return useSyncExternalStore( h => { if (pubKey) { - system.ProfileLoader.TrackMetadata(pubKey); + system.ProfileLoader.TrackKeys(pubKey); } const release = system.ProfileLoader.Cache.hook(h, pubKey); return () => { release(); if (pubKey) { - system.ProfileLoader.UntrackMetadata(pubKey); + system.ProfileLoader.UntrackKeys(pubKey); } }; }, diff --git a/packages/system/src/background-loader.ts b/packages/system/src/background-loader.ts new file mode 100644 index 000000000..f48be16c9 --- /dev/null +++ b/packages/system/src/background-loader.ts @@ -0,0 +1,136 @@ +import debug from "debug"; +import { FeedCache, removeUndefined } from "@snort/shared"; +import { SystemInterface, TaggedNostrEvent, RequestBuilder } from "."; + +export abstract class BackgroundLoader { + #system: SystemInterface; + #cache: FeedCache; + #log = debug(this.name()); + + /** + * List of pubkeys to fetch metadata for + */ + #wantsKeys = new Set(); + + /** + * Custom loader function for fetching data from alternative sources + */ + loaderFn?: (pubkeys: Array) => Promise>; + + constructor(system: SystemInterface, cache: FeedCache) { + this.#system = system; + this.#cache = cache; + this.#FetchMetadata(); + } + + get Cache() { + return this.#cache; + } + + /** + * Name of this loader service + */ + abstract name(): string; + + /** + * Handle fetched data + */ + abstract onEvent(e: Readonly): T | undefined; + + /** + * Get expire time as uxix milliseconds + */ + abstract getExpireCutoff(): number; + + /** + * Build subscription for missing keys + */ + protected abstract buildSub(missing: Array): RequestBuilder; + + /** + * Create a placeholder value when no data can be found + */ + protected abstract makePlaceholder(key: string): T | undefined; + + /** + * Start requesting a set of keys to be loaded + */ + TrackKeys(pk: string | Array) { + for (const p of Array.isArray(pk) ? pk : [pk]) { + this.#wantsKeys.add(p); + } + } + + /** + * Stop requesting a set of keys to be loaded + */ + UntrackKeys(pk: string | Array) { + for (const p of Array.isArray(pk) ? pk : [pk]) { + this.#wantsKeys.delete(p); + } + } + + /** + * Get object from cache or fetch if missing + */ + async fetch(key: string) { + const existing = this.Cache.get(key); + if (existing) { + return existing; + } else { + return await new Promise((resolve, reject) => { + this.TrackKeys(key); + const release = this.Cache.hook(() => { + const existing = this.Cache.getFromCache(key); + if (existing) { + resolve(existing); + release(); + this.UntrackKeys(key); + } + }, key); + }); + } + } + + async #FetchMetadata() { + const loading = [...this.#wantsKeys]; + await this.#cache.buffer(loading); + + const missing = loading.filter(a => (this.#cache.getFromCache(a)?.loaded ?? 0) < this.getExpireCutoff()); + if (missing.length > 0) { + this.#log("Fetching keys: %O", missing); + try { + const found = await this.#loadData(missing); + const noResult = removeUndefined( + missing.filter(a => !found.some(b => a === this.#cache.key(b))).map(a => this.makePlaceholder(a)), + ); + if (noResult.length > 0) { + await Promise.all(noResult.map(a => this.#cache.update(a))); + } + } catch (e) { + this.#log("Error: %O", e); + debugger; + } + } + + setTimeout(() => this.#FetchMetadata(), 500); + } + + async #loadData(missing: Array) { + if (this.loaderFn) { + const results = await this.loaderFn(missing); + await Promise.all(results.map(a => this.#cache.update(a))); + return results; + } else { + const v = await this.#system.Fetch(this.buildSub(missing), async e => { + for (const pe of e) { + const m = this.onEvent(pe); + if (m) { + await this.#cache.update(m); + } + } + }); + return removeUndefined(v.map(this.onEvent)); + } + } +} diff --git a/packages/system/src/cache/index.ts b/packages/system/src/cache/index.ts index b9ec1881e..e1315b757 100644 --- a/packages/system/src/cache/index.ts +++ b/packages/system/src/cache/index.ts @@ -44,8 +44,8 @@ export interface RelayMetrics { export interface UsersRelays { pubkey: string; - created_at: number; relays: FullRelaySettings[]; + created: number; loaded: number; } diff --git a/packages/system/src/cache/user-relays.ts b/packages/system/src/cache/user-relays.ts index 2e98ab5fe..35af4c7dc 100644 --- a/packages/system/src/cache/user-relays.ts +++ b/packages/system/src/cache/user-relays.ts @@ -19,7 +19,7 @@ export class UserRelaysCache extends FeedCache { newest(): number { let ret = 0; - this.cache.forEach(v => (ret = v.created_at > ret ? v.created_at : ret)); + this.cache.forEach(v => (ret = v.created > ret ? v.created : ret)); return ret; } diff --git a/packages/system/src/nostr-system.ts b/packages/system/src/nostr-system.ts index 84119bc3f..402d8096b 100644 --- a/packages/system/src/nostr-system.ts +++ b/packages/system/src/nostr-system.ts @@ -5,7 +5,7 @@ import { unwrap, sanitizeRelayUrl, FeedCache, removeUndefined } from "@snort/sha import { NostrEvent, TaggedNostrEvent } from "./nostr"; import { Connection, RelaySettings, ConnectionStateSnapshot, OkResponse } from "./connection"; import { Query } from "./query"; -import { NoteCollection, NoteStore, NoteStoreSnapshotData } from "./note-collection"; +import { NoteCollection, NoteStore } from "./note-collection"; import { BuiltRawReqFilter, RequestBuilder, RequestStrategy } from "./request-builder"; import { RelayMetricHandler } from "./relay-metric-handler"; import { @@ -22,7 +22,7 @@ import { EventExt, } from "."; import { EventsCache } from "./cache/events"; -import { RelayCache, pickRelaysForReply } from "./outbox-model"; +import { RelayCache, RelayMetadataLoader, pickRelaysForReply } from "./outbox-model"; import { QueryOptimizer, DefaultQueryOptimizer } from "./query-optimizer"; import { trimFilters } from "./request-trim"; @@ -88,6 +88,8 @@ export class NostrSystem extends EventEmitter implements Syst */ checkSigs: boolean; + #relayLoader: RelayMetadataLoader; + constructor(props: { relayCache?: FeedCache; profileCache?: FeedCache; @@ -106,6 +108,7 @@ export class NostrSystem extends EventEmitter implements Syst this.#profileLoader = new ProfileLoaderService(this, this.#profileCache); this.#relayMetrics = new RelayMetricHandler(this.#relayMetricsCache); + this.#relayLoader = new RelayMetadataLoader(this, this.#relayCache); this.checkSigs = props.checkSigs ?? true; this.#cleanup(); } @@ -333,6 +336,9 @@ export class NostrSystem extends EventEmitter implements Syst ); } } + if (f.authors) { + this.#relayLoader.TrackKeys(f.authors); + } } // check for empty filters diff --git a/packages/system/src/outbox-model.ts b/packages/system/src/outbox-model.ts index 83fd7fcba..c4bd5995c 100644 --- a/packages/system/src/outbox-model.ts +++ b/packages/system/src/outbox-model.ts @@ -3,6 +3,7 @@ import { dedupe, sanitizeRelayUrl, unixNowMs, unwrap } from "@snort/shared"; import debug from "debug"; import { FlatReqFilter } from "./query-optimizer"; import { RelayListCacheExpire } from "./const"; +import { BackgroundLoader } from "./background-loader"; const PickNRelays = 2; @@ -224,9 +225,44 @@ export async function updateRelayLists(authors: Array, system: SystemInt relayLists.map(a => ({ relays: parseRelayTags(a.tags), pubkey: a.pubkey, - created_at: a.created_at, + created: a.created_at, loaded: unixNowMs(), })), ); } } + +export class RelayMetadataLoader extends BackgroundLoader { + override name(): string { + return "RelayMetadataLoader"; + } + + override onEvent(e: Readonly): UsersRelays | undefined { + return { + relays: parseRelayTags(e.tags), + pubkey: e.pubkey, + created: e.created_at, + loaded: unixNowMs(), + }; + } + + override getExpireCutoff(): number { + return unixNowMs() - RelayListCacheExpire; + } + + protected override buildSub(missing: string[]): RequestBuilder { + const rb = new RequestBuilder("relay-loader"); + rb.withOptions({ skipDiff: true }); + rb.withFilter().authors(missing).kinds([EventKind.Relays]); + return rb; + } + + protected override makePlaceholder(key: string): UsersRelays | undefined { + return { + relays: [], + pubkey: key, + created: 0, + loaded: this.getExpireCutoff() + 300_000, + }; + } +} diff --git a/packages/system/src/profile-cache.ts b/packages/system/src/profile-cache.ts index ee5646c8d..84d2e511b 100644 --- a/packages/system/src/profile-cache.ts +++ b/packages/system/src/profile-cache.ts @@ -1,156 +1,40 @@ -import debug from "debug"; -import { unixNowMs, FeedCache } from "@snort/shared"; -import { EventKind, HexKey, SystemInterface, TaggedNostrEvent, RequestBuilder } from "."; +import { unixNowMs } from "@snort/shared"; +import { EventKind, TaggedNostrEvent, RequestBuilder } from "."; import { ProfileCacheExpire } from "./const"; import { mapEventToProfile, MetadataCache } from "./cache"; import { v4 as uuid } from "uuid"; +import { BackgroundLoader } from "./background-loader"; -const MetadataRelays = ["wss://purplepag.es"]; - -export class ProfileLoaderService { - #system: SystemInterface; - #cache: FeedCache; - - /** - * A set of pubkeys we could not find last run, - * This list will attempt to use known profile metadata relays - */ - #missingLastRun: Set = new Set(); - - /** - * List of pubkeys to fetch metadata for - */ - #wantsMetadata: Set = new Set(); - - readonly #log = debug("ProfileCache"); - - /** - * Custom loader function for fetching profiles from alternative sources - */ - loaderFn?: (pubkeys: Array) => Promise>; - - constructor(system: SystemInterface, cache: FeedCache) { - this.#system = system; - this.#cache = cache; - this.#FetchMetadata(); +export class ProfileLoaderService extends BackgroundLoader { + override name(): string { + return "ProfileLoaderService"; } - get Cache() { - return this.#cache; + override onEvent(e: Readonly): MetadataCache | undefined { + return mapEventToProfile(e); } - /** - * Request profile metadata for a set of pubkeys - */ - TrackMetadata(pk: HexKey | Array) { - for (const p of Array.isArray(pk) ? pk : [pk]) { - if (p.length === 64) { - this.#wantsMetadata.add(p); - } - } + override getExpireCutoff(): number { + return unixNowMs() - ProfileCacheExpire; } - /** - * Stop tracking metadata for a set of pubkeys - */ - UntrackMetadata(pk: HexKey | Array) { - for (const p of Array.isArray(pk) ? pk : [pk]) { - if (p.length > 0) { - this.#wantsMetadata.delete(p); - } - } + override buildSub(missing: string[]): RequestBuilder { + const sub = new RequestBuilder(`profiles-${uuid()}`); + sub + .withOptions({ + skipDiff: true, + }) + .withFilter() + .kinds([EventKind.SetMetadata]) + .authors(missing); + return sub; } - async onProfileEvent(e: Readonly) { - const profile = mapEventToProfile(e); - if (profile) { - await this.#cache.update(profile); - } - } - - async fetchProfile(key: string) { - const existing = this.Cache.get(key); - if (existing) { - return existing; - } else { - return await new Promise((resolve, reject) => { - this.TrackMetadata(key); - const release = this.Cache.hook(() => { - const existing = this.Cache.getFromCache(key); - if (existing) { - resolve(existing); - release(); - this.UntrackMetadata(key); - } - }, key); - }); - } - } - - async #FetchMetadata() { - const missingFromCache = await this.#cache.buffer([...this.#wantsMetadata]); - - const expire = unixNowMs() - ProfileCacheExpire; - const expired = [...this.#wantsMetadata] - .filter(a => !missingFromCache.includes(a)) - .filter(a => (this.#cache.getFromCache(a)?.loaded ?? 0) < expire); - const missing = new Set([...missingFromCache, ...expired]); - if (missing.size > 0) { - this.#log("Wants profiles: %d missing, %d expired", missingFromCache.length, expired.length); - - const results = await this.#loadProfiles([...missing]); - - const couldNotFetch = [...missing].filter(a => !results.some(b => b.pubkey === a)); - this.#missingLastRun = new Set(couldNotFetch); - if (couldNotFetch.length > 0) { - this.#log("No profiles: %o", couldNotFetch); - const empty = couldNotFetch.map(a => - this.#cache.update({ - pubkey: a, - loaded: unixNowMs() - ProfileCacheExpire + 30_000, // expire in 30s - created: 69, - } as MetadataCache), - ); - await Promise.all(empty); - } - - /* When we fetch an expired profile and its the same as what we already have - // onEvent is not fired and the loaded timestamp never gets updated - const expiredSame = results.filter(a => !newProfiles.has(a.id) && expired.includes(a.pubkey)); - await Promise.all(expiredSame.map(v => this.onProfileEvent(v)));*/ - } - - setTimeout(() => this.#FetchMetadata(), 500); - } - - async #loadProfiles(missing: Array) { - if (this.loaderFn) { - const results = await this.loaderFn(missing); - await Promise.all(results.map(a => this.#cache.update(a))); - return results; - } else { - const sub = new RequestBuilder(`profiles-${uuid()}`); - sub - .withOptions({ - skipDiff: true, - }) - .withFilter() - .kinds([EventKind.SetMetadata]) - .authors(missing); - - if (this.#missingLastRun.size > 0) { - const fMissing = sub - .withFilter() - .kinds([EventKind.SetMetadata]) - .authors([...this.#missingLastRun]); - MetadataRelays.forEach(r => fMissing.relay(r)); - } - const results = (await this.#system.Fetch(sub, async e => { - for (const pe of e) { - await this.onProfileEvent(pe); - } - })) as ReadonlyArray; - return results; - } + protected override makePlaceholder(key: string): MetadataCache | undefined { + return { + pubkey: key, + loaded: unixNowMs() - ProfileCacheExpire + 30_000, + created: 0, + } as MetadataCache; } } diff --git a/packages/system/src/relay-info.ts b/packages/system/src/relay-info.ts index d5bdbf626..106f57c6f 100644 --- a/packages/system/src/relay-info.ts +++ b/packages/system/src/relay-info.ts @@ -7,11 +7,12 @@ export interface RelayInfo { software?: string; version?: string; limitation?: { - payment_required: boolean; - max_subscriptions: number; - max_filters: number; - max_event_tags: number; - auth_required: boolean; + payment_required?: boolean; + max_subscriptions?: number; + max_filters?: number; + max_event_tags?: number; + auth_required?: boolean; + write_restricted?: boolean; }; relay_countries?: Array; language_tags?: Array; From 2884a35b5ce053484302e35981547ba70d752f05 Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 22 Nov 2023 15:34:46 +0000 Subject: [PATCH 04/21] chore: random fixes --- .../app/src/Element/SuggestedProfiles.tsx | 6 +- packages/app/src/Feed/LoginFeed.ts | 63 +++++++------------ packages/app/src/Hooks/useLists.tsx | 18 ++++-- packages/app/src/Pages/HashTagsPage.tsx | 13 ++-- packages/app/src/Pages/Profile/ProfileTab.tsx | 4 +- packages/system/src/event-kind.ts | 15 ++++- packages/system/src/event-publisher.ts | 5 +- packages/system/src/nostr-system.ts | 2 +- packages/system/src/outbox-model.ts | 15 ++++- 9 files changed, 75 insertions(+), 66 deletions(-) diff --git a/packages/app/src/Element/SuggestedProfiles.tsx b/packages/app/src/Element/SuggestedProfiles.tsx index 2f6351b54..72d057be3 100644 --- a/packages/app/src/Element/SuggestedProfiles.tsx +++ b/packages/app/src/Element/SuggestedProfiles.tsx @@ -16,7 +16,7 @@ enum Provider { } export default function SuggestedProfiles() { - const login = useLogin(); + const login = useLogin(s => ({ publicKey: s.publicKey, follows: s.follows.item })); const [userList, setUserList] = useState(); const [provider, setProvider] = useState(Provider.NostrBand); const [error, setError] = useState(); @@ -37,7 +37,7 @@ export default function SuggestedProfiles() { } case Provider.SemisolDev: { const api = new SemisolDevApi(); - const users = await api.sugguestedFollows(login.publicKey, login.follows.item); + const users = await api.sugguestedFollows(login.publicKey, login.follows); const keys = users.recommendations.sort(a => a[1]).map(a => a[0]); setUserList(keys); break; @@ -52,7 +52,7 @@ export default function SuggestedProfiles() { useEffect(() => { loadSuggestedProfiles(); - }, [login, provider]); + }, [login.publicKey, login.follows, provider]); return ( <> diff --git a/packages/app/src/Feed/LoginFeed.ts b/packages/app/src/Feed/LoginFeed.ts index 92f027645..0359f9941 100644 --- a/packages/app/src/Feed/LoginFeed.ts +++ b/packages/app/src/Feed/LoginFeed.ts @@ -1,11 +1,9 @@ import { useEffect, useMemo } from "react"; -import { TaggedNostrEvent, EventKind, RequestBuilder, NoteCollection, NostrLink } from "@snort/system"; +import { TaggedNostrEvent, EventKind, RequestBuilder, NoteCollection, NostrLink, parseRelayTags } from "@snort/system"; import { useRequestBuilder } from "@snort/system-react"; -import { bech32ToHex, debounce, findTag, getNewest, getNewestEventTagsByKey, unwrap } from "@/SnortUtils"; -import { makeNotification, sendNotification } from "@/Notifications"; +import { bech32ToHex, debounce, getNewest, getNewestEventTagsByKey, unwrap } from "@/SnortUtils"; import useEventPublisher from "@/Hooks/useEventPublisher"; -import useModeration from "@/Hooks/useModeration"; import useLogin from "@/Hooks/useLogin"; import { SnortAppData, @@ -24,14 +22,14 @@ import { SubscriptionEvent } from "@/Subscription"; import { FollowLists, FollowsFeed, GiftsCache, Notifications, UserRelays } from "@/Cache"; import { Nip28Chats, Nip4Chats } from "@/chat"; import { useRefreshFeedCache } from "@/Hooks/useRefreshFeedcache"; +import { usePrevious } from "@uidotdev/usehooks"; /** * Managed loading data for the current logged in user */ export default function useLoginFeed() { const login = useLogin(); - const { publicKey: pubKey, readNotifications, follows } = login; - const { isMuted } = useModeration(); + const { publicKey: pubKey, follows } = login; const { publisher, system } = useEventPublisher(); useRefreshFeedCache(Notifications, true); @@ -43,15 +41,19 @@ export default function useLoginFeed() { system.checkSigs = login.appData.item.preferences.checkSigs; }, [login]); + const previous = usePrevious(login.appData.item); // write appdata after 10s of no changes useEffect(() => { + if (!previous || JSON.stringify(previous) === JSON.stringify(login.appData.item)) { + return; + } return debounce(10_000, async () => { if (publisher && login.appData.item) { const ev = await publisher.appData(login.appData.item, "snort"); await system.BroadcastEvent(ev); } }); - }, [login.appData.timestamp]); + }, [previous]); const subLogin = useMemo(() => { if (!login || !pubKey) return null; @@ -62,8 +64,16 @@ export default function useLoginFeed() { }); b.withFilter() .authors([pubKey]) - .kinds([EventKind.ContactList, EventKind.Relays, EventKind.MuteList, EventKind.PinList]); - b.withFilter().authors([pubKey]).kinds([EventKind.CategorizedBookmarks]).tag("d", ["follow", "bookmark"]); + .kinds([ + EventKind.ContactList, + EventKind.Relays, + EventKind.MuteList, + EventKind.PinList, + EventKind.BookmarksList, + EventKind.InterestsList, + EventKind.PublicChatsList, + ]); + b.withFilter().authors([pubKey]).kinds([]); if (CONFIG.features.subscriptions && !login.readonly) { b.withFilter().authors([pubKey]).kinds([EventKind.AppData]).tag("d", ["snort"]); b.withFilter() @@ -100,17 +110,7 @@ export default function useLoginFeed() { const relays = getNewest(loginFeed.data.filter(a => a.kind === EventKind.Relays)); if (relays) { - const parsedRelays = relays.tags - .filter(a => a[0] === "r") - .map(a => { - return [ - a[1], - { - read: a[2] === "read" || a[2] === undefined, - write: a[2] === "write" || a[2] === undefined, - }, - ]; - }); + const parsedRelays = parseRelayTags(relays.tags.filter(a => a[0] === "r")).map(a => [a.url, a.settings]); setRelays(login, Object.fromEntries(parsedRelays), relays.created_at * 1000); } @@ -144,21 +144,6 @@ export default function useLoginFeed() { } }, [loginFeed, publisher]); - // send out notifications - useEffect(() => { - if (loginFeed.data) { - const replies = loginFeed.data.filter( - a => a.kind === EventKind.TextNote && !isMuted(a.pubkey) && a.created_at > readNotifications, - ); - replies.forEach(async nx => { - const n = await makeNotification(nx); - if (n) { - sendNotification(login, n); - } - }); - } - }, [loginFeed, readNotifications]); - async function handleMutedFeed(mutedFeed: TaggedNostrEvent[]) { const latest = getNewest(mutedFeed); if (!latest) return; @@ -211,14 +196,10 @@ export default function useLoginFeed() { const pinnedFeed = loginFeed.data.filter(a => a.kind === EventKind.PinList); handlePinnedFeed(pinnedFeed); - const tagsFeed = loginFeed.data.filter( - a => a.kind === EventKind.CategorizedBookmarks && findTag(a, "d") === "follow", - ); + const tagsFeed = loginFeed.data.filter(a => a.kind === EventKind.InterestsList); handleTagFeed(tagsFeed); - const bookmarkFeed = loginFeed.data.filter( - a => a.kind === EventKind.CategorizedBookmarks && findTag(a, "d") === "bookmark", - ); + const bookmarkFeed = loginFeed.data.filter(a => a.kind === EventKind.BookmarksList); handleBookmarkFeed(bookmarkFeed); } }, [loginFeed]); diff --git a/packages/app/src/Hooks/useLists.tsx b/packages/app/src/Hooks/useLists.tsx index 05c8d9ca4..6e73cdd92 100644 --- a/packages/app/src/Hooks/useLists.tsx +++ b/packages/app/src/Hooks/useLists.tsx @@ -27,7 +27,7 @@ export function useLinkListEvents(id: string, fn: (rb: RequestBuilder) => void) } export function usePinList(pubkey: string | undefined) { - return useLinkListEvents(`pins:${pubkey?.slice(0, 12)}`, rb => { + return useLinkListEvents(`list:pins:${pubkey?.slice(0, 12)}`, rb => { if (pubkey) { rb.withFilter().kinds([EventKind.PinList]).authors([pubkey]); } @@ -35,17 +35,25 @@ export function usePinList(pubkey: string | undefined) { } export function useMuteList(pubkey: string | undefined) { - return useLinkList(`pins:${pubkey?.slice(0, 12)}`, rb => { + return useLinkList(`list:mute:${pubkey?.slice(0, 12)}`, rb => { if (pubkey) { rb.withFilter().kinds([EventKind.MuteList]).authors([pubkey]); } }); } -export default function useCategorizedBookmarks(pubkey: string | undefined, list: string) { - return useLinkListEvents(`categorized-bookmarks:${list}:${pubkey?.slice(0, 12)}`, rb => { +export function useBookmarkList(pubkey: string | undefined) { + return useLinkListEvents(`list:bookmark:${pubkey?.slice(0, 12)}`, rb => { if (pubkey) { - rb.withFilter().kinds([EventKind.CategorizedBookmarks]).authors([pubkey]).tag("d", [list]); + rb.withFilter().kinds([EventKind.BookmarksList]).authors([pubkey]); + } + }); +} + +export function useInterestsList(pubkey: string | undefined) { + return useLinkList(`list:interest:${pubkey?.slice(0, 12)}`, rb => { + if (pubkey) { + rb.withFilter().kinds([EventKind.InterestsList]).authors([pubkey]); } }); } diff --git a/packages/app/src/Pages/HashTagsPage.tsx b/packages/app/src/Pages/HashTagsPage.tsx index 0905c499e..cd7c542c1 100644 --- a/packages/app/src/Pages/HashTagsPage.tsx +++ b/packages/app/src/Pages/HashTagsPage.tsx @@ -1,7 +1,7 @@ import { useMemo } from "react"; import { Link, useParams } from "react-router-dom"; import { FormattedMessage, FormattedNumber } from "react-intl"; -import { EventKind, NostrHashtagLink, NoteCollection, RequestBuilder } from "@snort/system"; +import { EventKind, NoteCollection, RequestBuilder } from "@snort/system"; import { dedupe } from "@snort/shared"; import { useRequestBuilder } from "@snort/system-react"; @@ -44,10 +44,11 @@ export function HashTagHeader({ tag, events, className }: { tag: string; events? async function followTags(ts: string[]) { if (publisher) { - const ev = await publisher.bookmarks( - ts.map(a => new NostrHashtagLink(a)), - "follow", - ); + const ev = await publisher.generic(eb => { + eb.kind(EventKind.InterestsList); + ts.forEach(a => eb.tag(["t", a])); + return eb; + }); setTags(login, ts, ev.created_at * 1000); await system.BroadcastEvent(ev); } @@ -55,7 +56,7 @@ export function HashTagHeader({ tag, events, className }: { tag: string; events? const sub = useMemo(() => { const rb = new RequestBuilder(`hashtag-counts:${tag}`); - rb.withFilter().kinds([EventKind.CategorizedBookmarks]).tag("d", ["follow"]).tag("t", [tag.toLowerCase()]); + rb.withFilter().kinds([EventKind.InterestsList]).tag("t", [tag.toLowerCase()]); return rb; }, [tag]); const followsTag = useRequestBuilder(NoteCollection, sub); diff --git a/packages/app/src/Pages/Profile/ProfileTab.tsx b/packages/app/src/Pages/Profile/ProfileTab.tsx index 721dd2737..ab363d1fc 100644 --- a/packages/app/src/Pages/Profile/ProfileTab.tsx +++ b/packages/app/src/Pages/Profile/ProfileTab.tsx @@ -13,7 +13,7 @@ import Bookmarks from "@/Element/User/Bookmarks"; import Icon from "@/Icons/Icon"; import { Tab } from "@/Element/Tabs"; import { default as ZapElement } from "@/Element/Event/Zap"; -import useCategorizedBookmarks from "@/Hooks/useLists"; +import { useBookmarkList } from "@/Hooks/useLists"; import messages from "../messages"; @@ -60,7 +60,7 @@ export function RelaysTab({ id }: { id: HexKey }) { } export function BookMarksTab({ id }: { id: HexKey }) { - const bookmarks = useCategorizedBookmarks(id, "bookmark"); + const bookmarks = useBookmarkList(id); const reactions = useReactions(`bookmark:reactions:{id}`, bookmarks.map(NostrLink.fromEvent)); return ; } diff --git a/packages/system/src/event-kind.ts b/packages/system/src/event-kind.ts index 579c56bc8..66aec1103 100644 --- a/packages/system/src/event-kind.ts +++ b/packages/system/src/event-kind.ts @@ -27,9 +27,20 @@ enum EventKind { MuteList = 10_000, // NIP-51 PinList = 10_001, // NIP-51 + BookmarksList = 10_003, // NIP-51 + CommunitiesList = 10_004, // NIP-51 + PublicChatsList = 10_005, // NIP-51 + BlockedRelaysList = 10_006, // NIP-51 + SearchRelaysList = 10_007, // NIP-51 + InterestsList = 10_015, // NIP-51 + EmojisList = 10_030, // NIP-51 - CategorizedPeople = 30000, // NIP-51a - CategorizedBookmarks = 30001, // NIP-51b + FollowSet = 30_000, // NIP-51 + RelaySet = 30_002, // NIP-51 + BookmarkSet = 30_003, // NIP-51 + CurationSet = 30_004, // NIP-51 + InterestSet = 30_015, // NIP-15 + EmojiSet = 30_030, // NIP-51 Badge = 30009, // NIP-58 ProfileBadges = 30008, // NIP-58 diff --git a/packages/system/src/event-publisher.ts b/packages/system/src/event-publisher.ts index 0ccc02453..14cc9f78e 100644 --- a/packages/system/src/event-publisher.ts +++ b/packages/system/src/event-publisher.ts @@ -137,9 +137,8 @@ export class EventPublisher { * Build a categorized bookmarks event with a given label * @param notes List of bookmarked links */ - async bookmarks(notes: Array, list: "bookmark" | "follow") { - const eb = this.#eb(EventKind.CategorizedBookmarks); - eb.tag(["d", list]); + async bookmarks(notes: Array) { + const eb = this.#eb(EventKind.BookmarksList); notes.forEach(n => { eb.tag(unwrap(n.toEventTag())); }); diff --git a/packages/system/src/nostr-system.ts b/packages/system/src/nostr-system.ts index 402d8096b..6d9948255 100644 --- a/packages/system/src/nostr-system.ts +++ b/packages/system/src/nostr-system.ts @@ -400,7 +400,7 @@ export class NostrSystem extends EventEmitter implements Syst } return; }), - ...replyRelays.filter(a => !socks.some(b => b.Address === a)).map(a => this.WriteOnceToRelay(a, ev)), + ...replyRelays.filter(a => !this.#sockets.has(a)).map(a => this.WriteOnceToRelay(a, ev)), ]); return removeUndefined(oks); } diff --git a/packages/system/src/outbox-model.ts b/packages/system/src/outbox-model.ts index c4bd5995c..8320f6870 100644 --- a/packages/system/src/outbox-model.ts +++ b/packages/system/src/outbox-model.ts @@ -1,5 +1,14 @@ -import { EventKind, FullRelaySettings, NostrEvent, ReqFilter, RequestBuilder, SystemInterface, UsersRelays } from "."; -import { dedupe, sanitizeRelayUrl, unixNowMs, unwrap } from "@snort/shared"; +import { + EventKind, + FullRelaySettings, + NostrEvent, + ReqFilter, + RequestBuilder, + SystemInterface, + TaggedNostrEvent, + UsersRelays, +} from "."; +import { dedupe, removeUndefined, sanitizeRelayUrl, unixNowMs, unwrap } from "@snort/shared"; import debug from "debug"; import { FlatReqFilter } from "./query-optimizer"; import { RelayListCacheExpire } from "./const"; @@ -193,7 +202,7 @@ export async function pickRelaysForReply(ev: NostrEvent, system: SystemInterface const recipients = dedupe(ev.tags.filter(a => a[0] === "p").map(a => a[1])); await updateRelayLists(recipients, system); const relays = pickTopRelays(system.RelayCache, recipients, 2, "read"); - const ret = dedupe(relays.map(a => a.relays).flat()); + const ret = removeUndefined(dedupe(relays.map(a => a.relays).flat())); logger("Picked %O from authors %O", ret, recipients); return ret; } From 6fd2741cc0ab5db2adfb7875e50b70ade163f8b4 Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 22 Nov 2023 15:52:30 +0000 Subject: [PATCH 05/21] fix: nip28 chats loading --- packages/app/src/chat/index.ts | 2 +- packages/app/src/chat/nip28.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/app/src/chat/index.ts b/packages/app/src/chat/index.ts index feb0e037c..c9be42ee0 100644 --- a/packages/app/src/chat/index.ts +++ b/packages/app/src/chat/index.ts @@ -204,6 +204,6 @@ export function useChatSystem() { return [...nip4, ...nip28].filter(a => { const authors = a.participants.filter(a => a.type === "pubkey").map(a => a.id); - return !authors.every(a => isBlocked(a)); + return authors.length === 0 || !authors.every(a => isBlocked(a)); }); } diff --git a/packages/app/src/chat/nip28.ts b/packages/app/src/chat/nip28.ts index 9a7a8adea..0b244427f 100644 --- a/packages/app/src/chat/nip28.ts +++ b/packages/app/src/chat/nip28.ts @@ -1,5 +1,5 @@ import debug from "debug"; -import { ExternalStore, FeedCache, unixNow, unwrap } from "@snort/shared"; +import { ExternalStore, FeedCache, unwrap } from "@snort/shared"; import { EventKind, NostrEvent, @@ -16,7 +16,6 @@ import { import { LoginSession } from "@/Login"; import { findTag } from "@/SnortUtils"; import { Chat, ChatParticipant, ChatSystem, ChatType, lastReadInChat } from "@/chat"; -import { Day } from "@/Const"; export class Nip28ChatSystem extends ExternalStore> implements ChatSystem { #cache: FeedCache; @@ -50,7 +49,7 @@ export class Nip28ChatSystem extends ExternalStore> implements ChatS const lastMessage = messages[id]?.reduce((acc, v) => (v.created_at > acc ? v.created_at : acc), 0) ?? 0; rb.withFilter() .tag("e", [id]) - .since(lastMessage === 0 ? unixNow() - 2 * Day : lastMessage) + .since(lastMessage === 0 ? undefined : lastMessage) .kinds(this.ChannelKinds); } @@ -67,9 +66,10 @@ export class Nip28ChatSystem extends ExternalStore> implements ChatS listChats(): Chat[] { const chats = this.#chatChannels(); - return Object.entries(chats).map(([k, v]) => { + const ret = Object.entries(chats).map(([k, v]) => { return Nip28ChatSystem.createChatObj(Nip28ChatSystem.chatId(k), v); }); + return ret; } static chatId(id: string) { From 5d9ca5ee39f57378846efa8e73d52fd1913ac8eb Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 22 Nov 2023 16:11:58 +0000 Subject: [PATCH 06/21] feat: sync public chats --- packages/app/src/Feed/LoginFeed.ts | 15 +++++++++++++++ packages/app/src/Pages/MessagesPage.tsx | 21 ++++++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/app/src/Feed/LoginFeed.ts b/packages/app/src/Feed/LoginFeed.ts index 0359f9941..13b7dfd76 100644 --- a/packages/app/src/Feed/LoginFeed.ts +++ b/packages/app/src/Feed/LoginFeed.ts @@ -6,6 +6,7 @@ import { bech32ToHex, debounce, getNewest, getNewestEventTagsByKey, unwrap } fro import useEventPublisher from "@/Hooks/useEventPublisher"; import useLogin from "@/Hooks/useLogin"; import { + LoginStore, SnortAppData, addSubscription, setAppData, @@ -23,6 +24,7 @@ import { FollowLists, FollowsFeed, GiftsCache, Notifications, UserRelays } from import { Nip28Chats, Nip4Chats } from "@/chat"; import { useRefreshFeedCache } from "@/Hooks/useRefreshFeedcache"; import { usePrevious } from "@uidotdev/usehooks"; +import { Nip28ChatSystem } from "@/chat/nip28"; /** * Managed loading data for the current logged in user @@ -188,6 +190,16 @@ export default function useLoginFeed() { } } + function handlePublicChatsListFeed(bookmarkFeed: TaggedNostrEvent[]) { + const newest = getNewestEventTagsByKey(bookmarkFeed, "e"); + if (newest) { + LoginStore.updateSession({ + ...login, + extraChats: newest.keys.map(Nip28ChatSystem.chatId), + }); + } + } + useEffect(() => { if (loginFeed.data) { const mutedFeed = loginFeed.data.filter(a => a.kind === EventKind.MuteList); @@ -201,6 +213,9 @@ export default function useLoginFeed() { const bookmarkFeed = loginFeed.data.filter(a => a.kind === EventKind.BookmarksList); handleBookmarkFeed(bookmarkFeed); + + const publicChatsFeed = loginFeed.data.filter(a => a.kind === EventKind.PublicChatsList); + handlePublicChatsListFeed(publicChatsFeed); } }, [loginFeed]); diff --git a/packages/app/src/Pages/MessagesPage.tsx b/packages/app/src/Pages/MessagesPage.tsx index 5db0330da..0ca77ad79 100644 --- a/packages/app/src/Pages/MessagesPage.tsx +++ b/packages/app/src/Pages/MessagesPage.tsx @@ -3,7 +3,7 @@ import "./MessagesPage.css"; import React, { useEffect, useMemo, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate, useParams } from "react-router-dom"; -import { NostrLink, TLVEntryType, UserMetadata, decodeTLV } from "@snort/system"; +import { EventKind, NostrLink, TLVEntryType, UserMetadata, decodeTLV } from "@snort/system"; import { useEventFeed, useUserProfile, useUserSearch } from "@snort/system-react"; import UnreadCount from "@/Element/UnreadCount"; @@ -25,6 +25,7 @@ import { LoginSession, LoginStore } from "@/Login"; import { Nip28ChatSystem } from "@/chat/nip28"; import { ChatParticipantProfile } from "@/Element/Chat/ChatParticipant"; import classNames from "classnames"; +import useEventPublisher from "@/Hooks/useEventPublisher"; const TwoCol = 768; const ThreeCol = 1500; @@ -182,6 +183,7 @@ function NewChatWindow() { const navigate = useNavigate(); const search = useUserSearch(); const login = useLogin(); + const { system, publisher } = useEventPublisher(); useEffect(() => { setNewChat([]); @@ -270,12 +272,25 @@ function NewChatWindow() { {results.length === 1 && ( { + onClick={async id => { setShow(false); + const chats = appendDedupe(login.extraChats, [Nip28ChatSystem.chatId(id)]); LoginStore.updateSession({ ...login, - extraChats: appendDedupe(login.extraChats, [Nip28ChatSystem.chatId(id)]), + extraChats: chats, } as LoginSession); + const evList = await publisher?.generic(eb => { + eb.kind(EventKind.PublicChatsList); + chats.forEach(c => { + if (c.startsWith("chat281")) { + eb.tag(["e", decodeTLV(c)[0].value as string]); + } + }); + return eb; + }); + if (evList) { + await system.BroadcastEvent(evList); + } navigate(createChatLink(ChatType.PublicGroupChat, id)); }} /> From 20c4ecaa0c480cb132215ee02a037540293639cf Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 22 Nov 2023 16:16:07 +0000 Subject: [PATCH 07/21] fix: topics --- packages/app/src/Pages/onboarding/topics.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/app/src/Pages/onboarding/topics.tsx b/packages/app/src/Pages/onboarding/topics.tsx index ad3f53bb0..b3d1fdfed 100644 --- a/packages/app/src/Pages/onboarding/topics.tsx +++ b/packages/app/src/Pages/onboarding/topics.tsx @@ -5,7 +5,7 @@ import AsyncButton from "@/Element/AsyncButton"; import classNames from "classnames"; import { appendDedupe } from "@/SnortUtils"; import useEventPublisher from "@/Hooks/useEventPublisher"; -import { NostrHashtagLink } from "@snort/system"; +import { EventKind } from "@snort/system"; export const FixedTopics = { life: { @@ -283,11 +283,14 @@ export function Topics() { const tags = Object.entries(FixedTopics) .filter(([k]) => topics.includes(k)) .map(([, v]) => v.tags) - .flat() - .map(a => new NostrHashtagLink(a)); + .flat(); if (tags.length > 0) { - const ev = await publisher?.bookmarks(tags, "follow"); + const ev = await publisher?.generic(eb => { + eb.kind(EventKind.InterestsList); + tags.forEach(a => eb.tag(["t", a])); + return eb; + }); if (ev) { await system.BroadcastEvent(ev); } From 0f07e759057e9211b1a2b148ad6a2b3b0734b36f Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 23 Nov 2023 12:43:20 +0000 Subject: [PATCH 08/21] fix: modal styles on mobile --- packages/app/src/Element/Modal.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/app/src/Element/Modal.css b/packages/app/src/Element/Modal.css index a08181f9d..8150bbc7a 100644 --- a/packages/app/src/Element/Modal.css +++ b/packages/app/src/Element/Modal.css @@ -20,6 +20,7 @@ margin-top: auto; margin-bottom: auto; --border-color: var(--gray); + max-width: 100%; } @media (min-width: 600px) { @@ -28,6 +29,11 @@ width: 600px; } } +@media (max-width: 600px) { + .modal-body { + min-width: 100%; + } +} .modal-body button.secondary:hover { background-color: var(--gray); From 3e8fe2ca958a96528cd42b167f7f0489fc8b037f Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 23 Nov 2023 13:09:23 +0000 Subject: [PATCH 09/21] fix: live stream embed layout --- packages/app/src/Element/Event/Note.tsx | 2 +- packages/app/src/Element/LiveEvent.tsx | 18 +++++++++--------- packages/system/src/event-kind.ts | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/app/src/Element/Event/Note.tsx b/packages/app/src/Element/Event/Note.tsx index 1b9105bb2..4eeae0bc0 100644 --- a/packages/app/src/Element/Event/Note.tsx +++ b/packages/app/src/Element/Event/Note.tsx @@ -50,7 +50,7 @@ export default function Note(props: NoteProps) { if (ev.kind === EventKind.ZapstrTrack) { return ; } - if (ev.kind === EventKind.CategorizedPeople || ev.kind === EventKind.ContactList) { + if (ev.kind === EventKind.FollowSet || ev.kind === EventKind.ContactList) { return ; } if (ev.kind === EventKind.LiveEvent) { diff --git a/packages/app/src/Element/LiveEvent.tsx b/packages/app/src/Element/LiveEvent.tsx index 1540dde4d..580dcca35 100644 --- a/packages/app/src/Element/LiveEvent.tsx +++ b/packages/app/src/Element/LiveEvent.tsx @@ -16,7 +16,7 @@ export function LiveEvent({ ev }: { ev: NostrEvent }) { switch (status) { case "live": { return ( -
+
@@ -49,7 +49,7 @@ export function LiveEvent({ ev }: { ev: NostrEvent }) { case "live": { return ( - @@ -59,7 +59,7 @@ export function LiveEvent({ ev }: { ev: NostrEvent }) { if (findTag(ev, "recording")) { return ( - @@ -70,13 +70,13 @@ export function LiveEvent({ ev }: { ev: NostrEvent }) { } return ( -
-
+
+
-
-

{title}

- {statusLine()} -
+
+
+
{title}
+
{statusLine()}
{cta()}
diff --git a/packages/system/src/event-kind.ts b/packages/system/src/event-kind.ts index 66aec1103..f169ba82f 100644 --- a/packages/system/src/event-kind.ts +++ b/packages/system/src/event-kind.ts @@ -1,4 +1,4 @@ -enum EventKind { +const enum EventKind { Unknown = -1, SetMetadata = 0, TextNote = 1, From 2c414c4a5624397256355b3a507690738849a985 Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 23 Nov 2023 13:43:23 +0000 Subject: [PATCH 10/21] feat: backup key task --- packages/app/src/Tasks/BackupKey.tsx | 49 +++++++++++++++++++++++ packages/app/src/Tasks/TaskList.tsx | 59 ++++++++++++++++++++-------- packages/app/src/Tasks/index.ts | 9 ++++- packages/app/vite.config.ts | 5 +-- 4 files changed, 100 insertions(+), 22 deletions(-) create mode 100644 packages/app/src/Tasks/BackupKey.tsx diff --git a/packages/app/src/Tasks/BackupKey.tsx b/packages/app/src/Tasks/BackupKey.tsx new file mode 100644 index 000000000..38009ffc9 --- /dev/null +++ b/packages/app/src/Tasks/BackupKey.tsx @@ -0,0 +1,49 @@ +import { FormattedMessage } from "react-intl"; +import { Link } from "react-router-dom"; +import { BaseUITask } from "@/Tasks"; +import { MetadataCache } from "@snort/system"; +import { LoginSession } from "@/Login"; +import Icon from "@/Icons/Icon"; + +export class BackupKeyTask extends BaseUITask { + id = "backup-key"; + noBaseStyle = true; + + check(_: MetadataCache, session: LoginSession): boolean { + return !this.state.muted && session.type == "private_key"; + } + + render() { + return ( +
+
+
+
+ +
+
+
+
+ +
+ + + +
+ + + + +
+
+
+
+ ); + } +} diff --git a/packages/app/src/Tasks/TaskList.tsx b/packages/app/src/Tasks/TaskList.tsx index 0fe477dcb..11cb82b4d 100644 --- a/packages/app/src/Tasks/TaskList.tsx +++ b/packages/app/src/Tasks/TaskList.tsx @@ -1,5 +1,5 @@ import "./TaskList.css"; -import { useState } from "react"; +import { useSyncExternalStore } from "react"; import { useUserProfile } from "@snort/system-react"; import useLogin from "@/Hooks/useLogin"; @@ -9,37 +9,62 @@ import { DonateTask } from "./DonateTask"; import { Nip5Task } from "./Nip5Task"; import { RenewSubTask } from "./RenewSubscription"; import { NoticeZapPoolDefault } from "./NoticeZapPool"; +import { BackupKeyTask } from "./BackupKey"; +import { ExternalStore } from "@snort/shared"; -const AllTasks: Array = [new Nip5Task(), new DonateTask(), new NoticeZapPoolDefault()]; -if (CONFIG.features.subscriptions) { - AllTasks.push(new RenewSubTask()); +class TaskStore extends ExternalStore> { + #tasks: Array; + + constructor() { + super(); + const AllTasks: Array = [ + new BackupKeyTask(), + new Nip5Task(), + new DonateTask(), + new NoticeZapPoolDefault() + ]; + if (CONFIG.features.subscriptions) { + AllTasks.push(new RenewSubTask()); + } + AllTasks.forEach(a => a.load(() => { + this.notifyChange() + })); + this.#tasks = AllTasks; + } + + takeSnapshot(): UITask[] { + return [...this.#tasks]; + } } -AllTasks.forEach(a => a.load()); +const AllTasks = new TaskStore(); export const TaskList = () => { const session = useLogin(); const user = useUserProfile(session.publicKey); - const [, setTick] = useState(0); + const tasks = useSyncExternalStore(c => AllTasks.hook(c), () => AllTasks.snapshot()); function muteTask(t: UITask) { t.mute(); - setTick(x => (x += 1)); } return (
- {AllTasks.filter(a => (user ? a.check(user, session) : false)).map(a => { - return ( -
-
- -
muteTask(a)}> - + {tasks.filter(a => (user ? a.check(user, session) : false)).map(a => { + if (a.noBaseStyle) { + return a.render(); + } else { + return ( +
+
+ +
muteTask(a)}> + +
+ {a.render()}
- {a.render()} -
- ); + ); + } })}
); diff --git a/packages/app/src/Tasks/index.ts b/packages/app/src/Tasks/index.ts index 73416e41c..a0bba5921 100644 --- a/packages/app/src/Tasks/index.ts +++ b/packages/app/src/Tasks/index.ts @@ -3,12 +3,13 @@ import { LoginSession } from "@/Login"; export interface UITask { id: string; + noBaseStyle: boolean; /** * Run checks to determine if this Task should be triggered for this user */ check(user: MetadataCache, session: LoginSession): boolean; mute(): void; - load(): void; + load(cb: () => void): void; render(): JSX.Element; } @@ -19,9 +20,11 @@ export interface UITaskState { } export abstract class BaseUITask implements UITask { + #cb?: () => void; protected state: UITaskState; abstract id: string; + noBaseStyle = false; abstract check(user: MetadataCache, session: LoginSession): boolean; abstract render(): JSX.Element; @@ -34,7 +37,8 @@ export abstract class BaseUITask implements UITask { this.#save(); } - load() { + load(cb: () => void) { + this.#cb = cb; const state = window.localStorage.getItem(`task:${this.id}`); if (state) { this.state = JSON.parse(state); @@ -43,5 +47,6 @@ export abstract class BaseUITask implements UITask { #save() { window.localStorage.setItem(`task:${this.id}`, JSON.stringify(this.state)); + this.#cb?.(); } } diff --git a/packages/app/vite.config.ts b/packages/app/vite.config.ts index ff798ef31..4d62cf83c 100644 --- a/packages/app/vite.config.ts +++ b/packages/app/vite.config.ts @@ -3,7 +3,6 @@ import { VitePWA } from "vite-plugin-pwa"; import { visualizer } from "rollup-plugin-visualizer"; import { defineConfig } from "vite"; import { vitePluginVersionMark } from "vite-plugin-version-mark"; - import appConfig from "config"; export default defineConfig({ @@ -27,13 +26,13 @@ export default defineConfig({ name: "snort", ifGitSHA: true, command: "git describe --always --tags", - }), + }) ], assetsInclude: ["**/*.md", "**/*.wasm"], build: { outDir: "build", }, - base: "", + clearScreen: false, publicDir: appConfig.get("publicDir"), resolve: { alias: { From 9cae8ec6eb4f3255e5e07393e919405e8ff3ff00 Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 23 Nov 2023 13:44:30 +0000 Subject: [PATCH 11/21] chore: formatting --- packages/app/src/Tasks/BackupKey.tsx | 78 ++++++++++++++------------- packages/app/src/Tasks/TaskList.tsx | 52 +++++++++--------- packages/app/src/lang.json | 12 +++++ packages/app/src/translations/en.json | 4 ++ packages/app/vite.config.ts | 2 +- 5 files changed, 85 insertions(+), 63 deletions(-) diff --git a/packages/app/src/Tasks/BackupKey.tsx b/packages/app/src/Tasks/BackupKey.tsx index 38009ffc9..54f011dd8 100644 --- a/packages/app/src/Tasks/BackupKey.tsx +++ b/packages/app/src/Tasks/BackupKey.tsx @@ -6,44 +6,48 @@ import { LoginSession } from "@/Login"; import Icon from "@/Icons/Icon"; export class BackupKeyTask extends BaseUITask { - id = "backup-key"; - noBaseStyle = true; + id = "backup-key"; + noBaseStyle = true; - check(_: MetadataCache, session: LoginSession): boolean { - return !this.state.muted && session.type == "private_key"; - } + check(_: MetadataCache, session: LoginSession): boolean { + return !this.state.muted && session.type == "private_key"; + } - render() { - return ( -
-
-
-
- -
-
-
-
- -
- - - -
- - - - -
-
-
+ render() { + return ( +
+
+
+
+
- ); - } +
+
+
+ +
+ + + +
+ + + + +
+
+
+
+ ); + } } diff --git a/packages/app/src/Tasks/TaskList.tsx b/packages/app/src/Tasks/TaskList.tsx index 11cb82b4d..babdcf8b8 100644 --- a/packages/app/src/Tasks/TaskList.tsx +++ b/packages/app/src/Tasks/TaskList.tsx @@ -17,18 +17,15 @@ class TaskStore extends ExternalStore> { constructor() { super(); - const AllTasks: Array = [ - new BackupKeyTask(), - new Nip5Task(), - new DonateTask(), - new NoticeZapPoolDefault() - ]; + const AllTasks: Array = [new BackupKeyTask(), new Nip5Task(), new DonateTask(), new NoticeZapPoolDefault()]; if (CONFIG.features.subscriptions) { AllTasks.push(new RenewSubTask()); } - AllTasks.forEach(a => a.load(() => { - this.notifyChange() - })); + AllTasks.forEach(a => + a.load(() => { + this.notifyChange(); + }), + ); this.#tasks = AllTasks; } @@ -41,7 +38,10 @@ const AllTasks = new TaskStore(); export const TaskList = () => { const session = useLogin(); const user = useUserProfile(session.publicKey); - const tasks = useSyncExternalStore(c => AllTasks.hook(c), () => AllTasks.snapshot()); + const tasks = useSyncExternalStore( + c => AllTasks.hook(c), + () => AllTasks.snapshot(), + ); function muteTask(t: UITask) { t.mute(); @@ -49,23 +49,25 @@ export const TaskList = () => { return (
- {tasks.filter(a => (user ? a.check(user, session) : false)).map(a => { - if (a.noBaseStyle) { - return a.render(); - } else { - return ( -
-
- -
muteTask(a)}> - + {tasks + .filter(a => (user ? a.check(user, session) : false)) + .map(a => { + if (a.noBaseStyle) { + return a.render(); + } else { + return ( +
+
+ +
muteTask(a)}> + +
+ {a.render()}
- {a.render()} -
- ); - } - })} + ); + } + })}
); }; diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json index 6182470c8..0bc008449 100644 --- a/packages/app/src/lang.json +++ b/packages/app/src/lang.json @@ -93,6 +93,9 @@ "1R43+L": { "defaultMessage": "Enter Nostr Wallet Connect config" }, + "1UWegE": { + "defaultMessage": "Be sure to back up your keys!" + }, "1c4YST": { "defaultMessage": "Connected to: {node} 🎉" }, @@ -907,6 +910,9 @@ "YDURw6": { "defaultMessage": "Service URL" }, + "YR2I9M": { + "defaultMessage": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute." + }, "YXA3AH": { "defaultMessage": "Enable reactions" }, @@ -1158,6 +1164,9 @@ "izWS4J": { "defaultMessage": "Unfollow" }, + "j9xbzF": { + "defaultMessage": "Already backed up" + }, "jA3OE/": { "defaultMessage": "{n,plural,=1{{n} sat} other{{n} sats}}" }, @@ -1343,6 +1352,9 @@ "r5srDR": { "defaultMessage": "Enter wallet password" }, + "rMgF34": { + "defaultMessage": "Back up now" + }, "rT14Ow": { "defaultMessage": "Add Relays" }, diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index 1d8a8cb75..0d98c0b51 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "Are you sure you want to remove this note from bookmarks?", "1R43+L": "Enter Nostr Wallet Connect config", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Connected to: {node} 🎉", "1nYUGC": "{n} Following", "1o2BgB": "Check Signatures", @@ -298,6 +299,7 @@ "Xopqkl": "Your default zap amount is {number} sats, example values are calculated from this.", "XrSk2j": "Redeem", "YDURw6": "Service URL", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Enable reactions", "Z4BMCZ": "Enter pairing phrase", "ZKORll": "Activate Now", @@ -381,6 +383,7 @@ "ieGrWo": "Follow", "itPgxd": "Profile", "izWS4J": "Unfollow", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} sat} other{{n} sats}}", "jAmfGl": "Your {site_name} subscription is expired", "jHa/ko": "Clean up your feed", @@ -442,6 +445,7 @@ "qz9fty": "Incorrect pin", "r3C4x/": "Software", "r5srDR": "Enter wallet password", + "rMgF34": "Back up now", "rT14Ow": "Add Relays", "rbrahO": "Close", "rfuMjE": "(Default)", diff --git a/packages/app/vite.config.ts b/packages/app/vite.config.ts index 4d62cf83c..964085543 100644 --- a/packages/app/vite.config.ts +++ b/packages/app/vite.config.ts @@ -26,7 +26,7 @@ export default defineConfig({ name: "snort", ifGitSHA: true, command: "git describe --always --tags", - }) + }), ], assetsInclude: ["**/*.md", "**/*.wasm"], build: { From 0a3e15df826b1c24dcd956bbbfaab01f08bb0484 Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 23 Nov 2023 13:45:44 +0000 Subject: [PATCH 12/21] chore: Update translations --- packages/app/src/translations/ar_SA.json | 4 ++++ packages/app/src/translations/az_AZ.json | 4 ++++ packages/app/src/translations/de_DE.json | 4 ++++ packages/app/src/translations/es_ES.json | 4 ++++ packages/app/src/translations/fa_IR.json | 4 ++++ packages/app/src/translations/fi_FI.json | 4 ++++ packages/app/src/translations/fr_FR.json | 4 ++++ packages/app/src/translations/hr_HR.json | 4 ++++ packages/app/src/translations/hu_HU.json | 4 ++++ packages/app/src/translations/id_ID.json | 4 ++++ packages/app/src/translations/it_IT.json | 4 ++++ packages/app/src/translations/ja_JP.json | 4 ++++ packages/app/src/translations/nl_NL.json | 4 ++++ packages/app/src/translations/pt_BR.json | 4 ++++ packages/app/src/translations/ru_RU.json | 4 ++++ packages/app/src/translations/sv_SE.json | 4 ++++ packages/app/src/translations/sw_KE.json | 4 ++++ packages/app/src/translations/ta_IN.json | 4 ++++ packages/app/src/translations/th_TH.json | 4 ++++ packages/app/src/translations/zh_CN.json | 4 ++++ packages/app/src/translations/zh_TW.json | 4 ++++ 21 files changed, 84 insertions(+) diff --git a/packages/app/src/translations/ar_SA.json b/packages/app/src/translations/ar_SA.json index f9c1d2486..82c88e6a1 100644 --- a/packages/app/src/translations/ar_SA.json +++ b/packages/app/src/translations/ar_SA.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "هل أنت متأكد من حذف هذا المنشور من المنشورات المرجعية؟", "1R43+L": "أدخل تكوين اتصال محفظة Nostr", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "متصل بـ: {node}🎉", "1nYUGC": "المتابَعون {n}", "1o2BgB": "التحقق من التوقيعات", @@ -298,6 +299,7 @@ "Xopqkl": "مبلغ زابي الافتراضي الخاص بك هو {number} جلسة، على سبيل المثال يتم حساب القيم من هذا.", "XrSk2j": "Redeem", "YDURw6": "رابط الخدمة", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "تمكين التفاعل", "Z4BMCZ": "أدخل عبارة الاقتران", "ZKORll": "نشط الآن", @@ -381,6 +383,7 @@ "ieGrWo": "متابعة", "itPgxd": "الملف التعريفي", "izWS4J": "الغاء المتابعة", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} ساتوشي}other{{n} ساتوشي}}", "jAmfGl": "إنتهت صلاحية اشتراكك {site_name}", "jHa/ko": "تنظيف موجز الويب الخاص بك", @@ -442,6 +445,7 @@ "qz9fty": "دبوس غير صحيح", "r3C4x/": "برنامج", "r5srDR": "أدخل كلمة مرور المحفظة", + "rMgF34": "Back up now", "rT14Ow": "إضافة موصّلات", "rbrahO": "أغلق", "rfuMjE": "(افتراضي)", diff --git a/packages/app/src/translations/az_AZ.json b/packages/app/src/translations/az_AZ.json index 4ed170c85..f90dc6942 100644 --- a/packages/app/src/translations/az_AZ.json +++ b/packages/app/src/translations/az_AZ.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "Bu qeydi əlfəcinlərdən silmək istədiyinizə əminsiniz?", "1R43+L": "Nostr Wallet Connect konfiqurasiyasını daxil edin", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Qoşuldu: {node} 🎉", "1nYUGC": "{n} İzləyir", "1o2BgB": "Check Signatures", @@ -298,6 +299,7 @@ "Xopqkl": "Your default zap amount is {number} sats, example values are calculated from this.", "XrSk2j": "Redeem", "YDURw6": "Service URL", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Enable reactions", "Z4BMCZ": "Enter pairing phrase", "ZKORll": "Activate Now", @@ -381,6 +383,7 @@ "ieGrWo": "Follow", "itPgxd": "Profile", "izWS4J": "Unfollow", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} sat} other{{n} sats}}", "jAmfGl": "Your {site_name} subscription is expired", "jHa/ko": "Clean up your feed", @@ -442,6 +445,7 @@ "qz9fty": "Incorrect pin", "r3C4x/": "Software", "r5srDR": "Enter wallet password", + "rMgF34": "Back up now", "rT14Ow": "Add Relays", "rbrahO": "Close", "rfuMjE": "(Default)", diff --git a/packages/app/src/translations/de_DE.json b/packages/app/src/translations/de_DE.json index 6c586532b..9acd2ff74 100644 --- a/packages/app/src/translations/de_DE.json +++ b/packages/app/src/translations/de_DE.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} Benutzer", "1Mo59U": "Bist du sicher, dass du diese Note aus deinen Lesezeichen entfernen möchtest?", "1R43+L": "Nostr Wallet Connect Konfiguration eingeben", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Verbunden mit: {node}🎉", "1nYUGC": "{n} Folgen", "1o2BgB": "Signaturen prüfen", @@ -298,6 +299,7 @@ "Xopqkl": "Dein standardmäßiger Zap-Betrag ist {number} sats, Beispielwerte werden daraus berechnet.", "XrSk2j": "Einlösen", "YDURw6": "URL des Dienstes", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Reaktionen aktivieren", "Z4BMCZ": "Verbindungs-Passphrase eingeben", "ZKORll": "Jetzt aktivieren", @@ -381,6 +383,7 @@ "ieGrWo": "Folgen", "itPgxd": "Profil", "izWS4J": "Entfolgen", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,one {}=1{{n} sat} other{{n} sats}}", "jAmfGl": "Dein {site_name} Abonnement ist abgelaufen", "jHa/ko": "Räume deinen Feed auf", @@ -442,6 +445,7 @@ "qz9fty": "Falsche PIN", "r3C4x/": "Software", "r5srDR": "Wallet Passwort eingeben", + "rMgF34": "Back up now", "rT14Ow": "Relais hinzufügen", "rbrahO": "Schließen", "rfuMjE": "(Standard)", diff --git a/packages/app/src/translations/es_ES.json b/packages/app/src/translations/es_ES.json index 9ea744c49..aca257d98 100644 --- a/packages/app/src/translations/es_ES.json +++ b/packages/app/src/translations/es_ES.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "¿Estás seguro de que quieres eliminar esta nota de tus favoritos?", "1R43+L": "Introduzca la configuración de Nostr Wallet Connect", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Conectado a: {node}🎉", "1nYUGC": "{n} Siguiendo", "1o2BgB": "Firmas de cheques", @@ -298,6 +299,7 @@ "Xopqkl": "Tu cantidad de zap por defecto es {number} sats, los valores de ejemplo se calculan a partir de esto.", "XrSk2j": "Canjear", "YDURw6": "URL del Servicio", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Activar reacciones", "Z4BMCZ": "Introduce la frase de emparejamiento", "ZKORll": "Activar Ahora", @@ -381,6 +383,7 @@ "ieGrWo": "Seguir", "itPgxd": "Perfil", "izWS4J": "No seguir", + "j9xbzF": "Already backed up", "jA3OE/": "{n} {n, plural, =1 {sat} other {sats}}", "jAmfGl": "Su suscripción a {site_name} ha caducado", "jHa/ko": "Limpie su alimentación", @@ -442,6 +445,7 @@ "qz9fty": "Clavija incorrecta", "r3C4x/": "Software", "r5srDR": "Introducir contraseña de cartera", + "rMgF34": "Back up now", "rT14Ow": "Añadir Relays", "rbrahO": "Cerrar", "rfuMjE": "(Por defecto)", diff --git a/packages/app/src/translations/fa_IR.json b/packages/app/src/translations/fa_IR.json index 8252c9f11..f1ea9cd89 100644 --- a/packages/app/src/translations/fa_IR.json +++ b/packages/app/src/translations/fa_IR.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "مطمئنید می خواهید این یادداشت را از نشانک ها خارج کنید؟", "1R43+L": "پیکربندی اتصال ناستر به کیف پول", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "متصل به:{node}🎉", "1nYUGC": "{n} دنبال شونده", "1o2BgB": "بررسی امضا", @@ -298,6 +299,7 @@ "Xopqkl": "مبلغ پیش فرض زپ شما {number} ساتوشی است، مقادیر نمونه از روی این محاسبه شده اند.", "XrSk2j": "بازخرید", "YDURw6": "خدمات URL", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "فعال سازی واکنش ها", "Z4BMCZ": "عبارت جفت را وارد کنید", "ZKORll": "اکنون فعال کن", @@ -381,6 +383,7 @@ "ieGrWo": "دنبال کردن", "itPgxd": "نمایه", "izWS4J": "لغو دنبال کردن", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,one {}=1{{n} نوت جدید} other{{n} نوت جدید}}", "jAmfGl": "عضویت شما در {site_name} منقضی شده است", "jHa/ko": "پاک سازی خبرنامه", @@ -442,6 +445,7 @@ "qz9fty": "پین نادرست", "r3C4x/": "نرم افزار", "r5srDR": "رمز کیف‌پول را وارد کنید", + "rMgF34": "Back up now", "rT14Ow": "افزودن رله", "rbrahO": "بستن", "rfuMjE": "(پیش‌فرض)", diff --git a/packages/app/src/translations/fi_FI.json b/packages/app/src/translations/fi_FI.json index aaf9749a5..040393083 100644 --- a/packages/app/src/translations/fi_FI.json +++ b/packages/app/src/translations/fi_FI.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "Haluatko varmasti poistaa tämän viestin kirjanmerkeistä?", "1R43+L": "Anna Nostr Wallet Connect asetukset", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Yhdistetty: {node} 🎉", "1nYUGC": "{n} Seuraa", "1o2BgB": "Tarkista allekirjoitukset", @@ -298,6 +299,7 @@ "Xopqkl": "Oletus zap-määräsi on {number} satsia, esimerkit on laskettu tämän mukaan.", "XrSk2j": "Lunasta", "YDURw6": "Palvelun URL", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Ota reaktiot käyttöön", "Z4BMCZ": "Anna pariliitoslause", "ZKORll": "Aktivoi nyt", @@ -381,6 +383,7 @@ "ieGrWo": "Seuraa", "itPgxd": "Profiili", "izWS4J": "Lopeta seuraaminen", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} sat} other{{n} satia}}", "jAmfGl": "{site_name} tilauksesi on päättynyt", "jHa/ko": "Siivoa rehusi", @@ -442,6 +445,7 @@ "qz9fty": "Väärä tappi", "r3C4x/": "Ohjelmisto", "r5srDR": "Anna lompakon salasana", + "rMgF34": "Back up now", "rT14Ow": "Lisää välittäjiä", "rbrahO": "Sulje", "rfuMjE": "(Oletus)", diff --git a/packages/app/src/translations/fr_FR.json b/packages/app/src/translations/fr_FR.json index 953553af4..658a089c2 100644 --- a/packages/app/src/translations/fr_FR.json +++ b/packages/app/src/translations/fr_FR.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "Êtes-vous sûr de vouloir supprimer cette note de vos favoris ?", "1R43+L": "Accéder à la configuration de Nostr Wallet Connect", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Connecté à : {node} 🎉", "1nYUGC": "{n} Abonnements", "1o2BgB": "Signatures de chèques", @@ -298,6 +299,7 @@ "Xopqkl": "Votre montant de zap par défaut est {number} sats, les valeurs d'exemple sont calculées à partir de ceci.", "XrSk2j": "Retirer", "YDURw6": "URL de service", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Activer les réactions", "Z4BMCZ": "Entrez la phrase d'appairage", "ZKORll": "Activer Maintenant", @@ -381,6 +383,7 @@ "ieGrWo": "Suivre", "itPgxd": "Profil", "izWS4J": "Ne plus suivre", + "j9xbzF": "Already backed up", "jA3OE/": "{n} {n, plural, =1 {sat} other {sats}}", "jAmfGl": "Votre abonnement à {site_name} a expiré", "jHa/ko": "Nettoyez votre flux", @@ -442,6 +445,7 @@ "qz9fty": "Broche incorrecte", "r3C4x/": "Logiciel", "r5srDR": "Entrez le mot de passe du portefeuille", + "rMgF34": "Back up now", "rT14Ow": "Ajouter Relais", "rbrahO": "Fermer", "rfuMjE": "(Défaut)", diff --git a/packages/app/src/translations/hr_HR.json b/packages/app/src/translations/hr_HR.json index dda80bf0f..de47e9245 100644 --- a/packages/app/src/translations/hr_HR.json +++ b/packages/app/src/translations/hr_HR.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "Jeste li sigurni da želite ukloniti ovu bilješku iz trake s oznakama?", "1R43+L": "Enter Nostr Wallet Connect config", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Povezano s: {node} 🎉", "1nYUGC": "{n} Pratitelja", "1o2BgB": "Check Signatures", @@ -298,6 +299,7 @@ "Xopqkl": "Your default zap amount is {number} sats, example values are calculated from this.", "XrSk2j": "Redeem", "YDURw6": "URL usluge", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Omogući reakcije", "Z4BMCZ": "Unesite frazu za uparivanje", "ZKORll": "Aktivirajte Odmah", @@ -381,6 +383,7 @@ "ieGrWo": "Prati", "itPgxd": "Profil", "izWS4J": "Prestani pratiti", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} sat} other{{n} sats}}", "jAmfGl": "Your {site_name} subscription is expired", "jHa/ko": "Clean up your feed", @@ -442,6 +445,7 @@ "qz9fty": "Incorrect pin", "r3C4x/": "Software", "r5srDR": "Unesite lozinku novčanika", + "rMgF34": "Back up now", "rT14Ow": "Dodaj relej", "rbrahO": "Close", "rfuMjE": "(Zadano)", diff --git a/packages/app/src/translations/hu_HU.json b/packages/app/src/translations/hu_HU.json index 7877f033b..deed22bfc 100644 --- a/packages/app/src/translations/hu_HU.json +++ b/packages/app/src/translations/hu_HU.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} felhasználó", "1Mo59U": "Biztos hogy a kedvencekből ezt a bejegyzést el akarod távolítani?", "1R43+L": "Írd be a Nostr Wallet Connect konfigurációt", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Kapcsolódás a: {node} 🎉", "1nYUGC": "{n} Követek", "1o2BgB": "Aláírások ellenörzése", @@ -298,6 +299,7 @@ "Xopqkl": "Az alapértelmezett zap összeg {number} sats, a példaértékek kiszámítása ebből történik.", "XrSk2j": "Beváltás", "YDURw6": "Szervíz cím", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Reakciók engedélyezése", "Z4BMCZ": "Párosító kifejezés megadása", "ZKORll": "Aktiválás", @@ -381,6 +383,7 @@ "ieGrWo": "Követem", "itPgxd": "Profil", "izWS4J": "Követés visszavonása", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,one {}=1{{n} sat} other{{n} sats}}", "jAmfGl": "{site_name}-előfizetése lejárt", "jHa/ko": "Tisztítsa meg a takarmányt", @@ -442,6 +445,7 @@ "qz9fty": "Helytelen Pin-kód", "r3C4x/": "Szoftver", "r5srDR": "Add meg a pénztárcád jelszavát", + "rMgF34": "Back up now", "rT14Ow": "Csomópont hozzáadása", "rbrahO": "Bezárás", "rfuMjE": "(Alapértelmezett)", diff --git a/packages/app/src/translations/id_ID.json b/packages/app/src/translations/id_ID.json index c83b6eff9..50bd559f1 100644 --- a/packages/app/src/translations/id_ID.json +++ b/packages/app/src/translations/id_ID.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "Apa Anda yakin Anda ingin memindahkan catatan ini dari penanda buku?", "1R43+L": "Masukkan konfigurasi Nostr Wallet Connect", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Terhubung ke: {node} 🎉", "1nYUGC": "{n} Mengikuti", "1o2BgB": "Periksa Tanda Tangan", @@ -298,6 +299,7 @@ "Xopqkl": "Jumlah zap default Anda adalah {number} sats, nilai contoh dihitung dari ini.", "XrSk2j": "Tebus", "YDURw6": "URL layanan", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Aktifkan reaksi", "Z4BMCZ": "Masukkan frasa pasangan", "ZKORll": "Aktifkan Sekarang", @@ -381,6 +383,7 @@ "ieGrWo": "Ikuti", "itPgxd": "Profil", "izWS4J": "Berhenti mengikuti", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} sat} other{{n} sat}}", "jAmfGl": "Langganan {site_name} Anda telah kedaluwarsa", "jHa/ko": "Bersihkan feed Anda", @@ -442,6 +445,7 @@ "qz9fty": "Pin salah", "r3C4x/": "Perangkat lunak", "r5srDR": "Masukkan kata sandi dompet", + "rMgF34": "Back up now", "rT14Ow": "Tambahkan Relay", "rbrahO": "Tutup", "rfuMjE": "(Bawaan)", diff --git a/packages/app/src/translations/it_IT.json b/packages/app/src/translations/it_IT.json index 6cf37cb43..fdaec964b 100644 --- a/packages/app/src/translations/it_IT.json +++ b/packages/app/src/translations/it_IT.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "Vuoi davvero rimuovere questa nota dai preferiti?", "1R43+L": "Inserire la configurazione di Nostr Wallet Connect", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Connessione a: {node}🎉", "1nYUGC": "{n} seguiti", "1o2BgB": "Firma degli assegni", @@ -298,6 +299,7 @@ "Xopqkl": "La quantità di zap predefinita è {number} sats, i valori di esempio sono calcolati da questo valore.", "XrSk2j": "Riscatto", "YDURw6": "URL del servizio", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Abilita reazioni", "Z4BMCZ": "Inserisci la frase di accoppiamento", "ZKORll": "Attiva adesso", @@ -381,6 +383,7 @@ "ieGrWo": "Segui", "itPgxd": "Profilo", "izWS4J": "Smetti di seguire", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} sat} other{{n} sats}}", "jAmfGl": "L'abbonamento a {site_name} è scaduto", "jHa/ko": "Pulite il vostro feed", @@ -442,6 +445,7 @@ "qz9fty": "Pin non corretto", "r3C4x/": "Software", "r5srDR": "Inserire la password del portafogli", + "rMgF34": "Back up now", "rT14Ow": "Aggiungi Relays", "rbrahO": "Chiudere", "rfuMjE": "(Predefinito)", diff --git a/packages/app/src/translations/ja_JP.json b/packages/app/src/translations/ja_JP.json index 92d9b3f01..4d748646d 100644 --- a/packages/app/src/translations/ja_JP.json +++ b/packages/app/src/translations/ja_JP.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "本当にこの投稿をブックマークから削除しますか?", "1R43+L": "Nostr Wallet Connectの設定値を入力", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "{node}に接続しました🎉", "1nYUGC": "{n} フォロー", "1o2BgB": "小切手署名", @@ -298,6 +299,7 @@ "Xopqkl": "デフォルトのザップ量は{number} satsで、例示された値はここから算出されます。", "XrSk2j": "交換", "YDURw6": "サービスURL", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "リアクションを有効にする", "Z4BMCZ": "ペアリングフレーズを入力", "ZKORll": "今すぐ有効化", @@ -381,6 +383,7 @@ "ieGrWo": "フォロー", "itPgxd": "プロフィール", "izWS4J": "フォロー解除", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} sat} other{{n} sats}}", "jAmfGl": "{site_name} 、有効期限が切れています。", "jHa/ko": "フィードをきれいにする", @@ -442,6 +445,7 @@ "qz9fty": "ピンが正しくない", "r3C4x/": "ソフトウェア", "r5srDR": "ウォレットのパスワードを入力", + "rMgF34": "Back up now", "rT14Ow": "リレーを追加する", "rbrahO": "閉じる", "rfuMjE": "(デフォルト)", diff --git a/packages/app/src/translations/nl_NL.json b/packages/app/src/translations/nl_NL.json index c8d61f22d..93d9916da 100644 --- a/packages/app/src/translations/nl_NL.json +++ b/packages/app/src/translations/nl_NL.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "Weet u zeker dat u deze notitie uit bladwijzers wilt verwijderen?", "1R43+L": "Voer Nostr Wallet Connect configuratie in", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Verbonden met: {node}🎉", "1nYUGC": "{n} Volgend", "1o2BgB": "Handtekeningen controleren", @@ -298,6 +299,7 @@ "Xopqkl": "Uw standaard zap bedrag is {number} sats, voorbeeldwaarden worden hiermee berekend.", "XrSk2j": "Inwisselen", "YDURw6": "Service-URL", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Reacties inschakelen", "Z4BMCZ": "Voer koppelingszin in", "ZKORll": "Activeer Nu", @@ -381,6 +383,7 @@ "ieGrWo": "Volgen", "itPgxd": "Profiel", "izWS4J": "Ontvolgen", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,one {}=1{{n} sat} other{{n} sats}}", "jAmfGl": "Uw {site_name} abonnement is verlopen", "jHa/ko": "Ruim je feed op", @@ -442,6 +445,7 @@ "qz9fty": "Verkeerde pin", "r3C4x/": "Software", "r5srDR": "Voer wallet wachtwoord in", + "rMgF34": "Back up now", "rT14Ow": "Relays toevoegen", "rbrahO": "Sluit", "rfuMjE": "(Standaard)", diff --git a/packages/app/src/translations/pt_BR.json b/packages/app/src/translations/pt_BR.json index 53b5d8215..a132324c5 100644 --- a/packages/app/src/translations/pt_BR.json +++ b/packages/app/src/translations/pt_BR.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "Tem certeza que deseja remover esta nota dos favoritos?", "1R43+L": "Insira a configuração da Nostr Wallet Connect", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Conectado em: {node} 🎉", "1nYUGC": "{n} Seguindo", "1o2BgB": "Assinaturas de cheques", @@ -298,6 +299,7 @@ "Xopqkl": "Sua quantidade padrão de zap é de {number} sats, valores de exemplo são calculados a partir disso.", "XrSk2j": "Resgatar", "YDURw6": "URL do serviço", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Habilitar reações", "Z4BMCZ": "Inserir frase de pareamento", "ZKORll": "Ativar agora", @@ -381,6 +383,7 @@ "ieGrWo": "Seguir", "itPgxd": "Perfil", "izWS4J": "Deixar de seguir", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} sat} other{{n} sats}}", "jAmfGl": "Sua assinatura do {site_name} expirou", "jHa/ko": "Limpe seu feed", @@ -442,6 +445,7 @@ "qz9fty": "Pino incorreto", "r3C4x/": "Software", "r5srDR": "Digite a senha da carteira", + "rMgF34": "Back up now", "rT14Ow": "Adicionar Relés", "rbrahO": "Fechar", "rfuMjE": "(Padrão)", diff --git a/packages/app/src/translations/ru_RU.json b/packages/app/src/translations/ru_RU.json index 42a397e38..30ca4ea66 100644 --- a/packages/app/src/translations/ru_RU.json +++ b/packages/app/src/translations/ru_RU.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "Вы уверены, что хотите удалить эту заметку из закладок?", "1R43+L": "Введите конфигурацию Nostr Wallet Connect", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Подключен к: {node} 🎉", "1nYUGC": "{n} Подписчиков", "1o2BgB": "Контрольные подписи", @@ -298,6 +299,7 @@ "Xopqkl": "По умолчанию величина zap равна {number} sats, примерные значения рассчитываются исходя из этого.", "XrSk2j": "Получить", "YDURw6": "URL-адрес сервиса", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Включить реакции", "Z4BMCZ": "Введи фразу для сопряжения", "ZKORll": "Активировать", @@ -381,6 +383,7 @@ "ieGrWo": "Подписаться", "itPgxd": "Профиль", "izWS4J": "Отписаться", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} sat} other{{n} sats}}", "jAmfGl": "Срок действия вашей подписки {site_name} истек", "jHa/ko": "Очистка корма", @@ -442,6 +445,7 @@ "qz9fty": "Неправильный вывод", "r3C4x/": "Программное обеспечение", "r5srDR": "Введите пароль кошелька", + "rMgF34": "Back up now", "rT14Ow": "Добавить реле", "rbrahO": "Закрыть", "rfuMjE": "(по умолчанию)", diff --git a/packages/app/src/translations/sv_SE.json b/packages/app/src/translations/sv_SE.json index 0d72edd7f..977e44754 100644 --- a/packages/app/src/translations/sv_SE.json +++ b/packages/app/src/translations/sv_SE.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "Är du säker på att du vill ta bort den här anteckningen från bokmärken?", "1R43+L": "Skriv in Nostr Wallet Connect konfiguration", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Ansluten till: {node}🎉", "1nYUGC": "{n} Följer", "1o2BgB": "Kontrollera signaturer", @@ -298,6 +299,7 @@ "Xopqkl": "Ditt förvalda zap-belopp är {number} sats, exempelvärden beräknas utifrån detta.", "XrSk2j": "Lös in", "YDURw6": "Service URL", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Aktivera reaktioner", "Z4BMCZ": "Ange parningsfras", "ZKORll": "Aktivera nu", @@ -381,6 +383,7 @@ "ieGrWo": "Följ", "itPgxd": "Profil", "izWS4J": "Sluta följ", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} sat} other{{n} sats}}", "jAmfGl": "Ditt {site_name} abonnemang har löpt ut", "jHa/ko": "Städa upp i ditt flöde", @@ -442,6 +445,7 @@ "qz9fty": "Felaktig pin", "r3C4x/": "Mjukvara", "r5srDR": "Ange lösenord för plånboken", + "rMgF34": "Back up now", "rT14Ow": "Lägg till reläer", "rbrahO": "Stäng", "rfuMjE": "(Standard)", diff --git a/packages/app/src/translations/sw_KE.json b/packages/app/src/translations/sw_KE.json index cfb048813..90bdb86ba 100644 --- a/packages/app/src/translations/sw_KE.json +++ b/packages/app/src/translations/sw_KE.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "Je, una uhakika unataka kuondoa dokezo hili kutoka kwa vialamisho?", "1R43+L": "Ingiza usanidi wa Nostr Wallet Connect", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "Imeunganishwa kwa: {node} 🎉", "1nYUGC": "{n} Unafuata", "1o2BgB": "Check Signatures", @@ -298,6 +299,7 @@ "Xopqkl": "Kiasi chako chaguomsingi cha zap ni {number} sats, thamani za mfano zinakokotolewa kutoka hii.", "XrSk2j": "Komboa", "YDURw6": "URL ya huduma", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "Washa maitikio", "Z4BMCZ": "Weka maneno ya kuoanisha", "ZKORll": "Washa Sasa", @@ -381,6 +383,7 @@ "ieGrWo": "Fuata", "itPgxd": "Wasifu", "izWS4J": "Acha kufuata", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,one {}=1{{n} sat} other{{n} sats}}", "jAmfGl": "Your {site_name} subscription is expired", "jHa/ko": "Clean up your feed", @@ -442,6 +445,7 @@ "qz9fty": "Incorrect pin", "r3C4x/": "Programu", "r5srDR": "Ingiza nenosiri la pochi", + "rMgF34": "Back up now", "rT14Ow": "Ongeza Relay", "rbrahO": "Close", "rfuMjE": "(Chaguo-msingi)", diff --git a/packages/app/src/translations/ta_IN.json b/packages/app/src/translations/ta_IN.json index 17eb9de35..8901b9521 100644 --- a/packages/app/src/translations/ta_IN.json +++ b/packages/app/src/translations/ta_IN.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "இந்தக் குறிப்பைப் புக்மார்க்குகளிலிருந்து அகற்ற நிச்சயமாக விரும்புகிறீர்களா?", "1R43+L": "நாஸ்டர் பணப்பை இணைப்புக் கட்டமைப்பை உள்ளிடவும்", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "{node} உடன் இணைக்கப் பட்டது 🎉", "1nYUGC": "{n} பின்தொடரப் படுவோர்", "1o2BgB": "கையெழுத்துக்களை சரிபார்", @@ -298,6 +299,7 @@ "Xopqkl": "Your default zap amount is {number} sats, example values are calculated from this.", "XrSk2j": "Redeem", "YDURw6": "சேவை URL", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "எதிர்வினைகளை அனுமதி", "Z4BMCZ": "இணைத்தல் சொற்றொடரை உள்ளிடவும்", "ZKORll": "இப்போது செயல்படுத்துக", @@ -381,6 +383,7 @@ "ieGrWo": "பின்தொடர்", "itPgxd": "சுயவிவரம்", "izWS4J": "பின்தொடர்வதை நிறுத்துக", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} ஸாட்} other{{n} ஸாட்கள்}}", "jAmfGl": "Your {site_name} subscription is expired", "jHa/ko": "Clean up your feed", @@ -442,6 +445,7 @@ "qz9fty": "Incorrect pin", "r3C4x/": "மென்பொருள்", "r5srDR": "பணப்பையின் கடவுச்சொல்லை உள்ளிடவும்", + "rMgF34": "Back up now", "rT14Ow": "ரிலேகளைச் சேர்க்கவும்", "rbrahO": "Close", "rfuMjE": "(இயல்புநிலை)", diff --git a/packages/app/src/translations/th_TH.json b/packages/app/src/translations/th_TH.json index 0092f2cbd..eb2857641 100644 --- a/packages/app/src/translations/th_TH.json +++ b/packages/app/src/translations/th_TH.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "คุณแน่ใจหรือว่าต้องการลบโน้ตนี้ออกจากบุ๊คมาร์ค?", "1R43+L": "ใส่ Nostr Wallet Connect config", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "เชื่อมต่อกับ: {node} 🎉", "1nYUGC": "กำลังติดตาม {n}", "1o2BgB": "Check Signatures", @@ -298,6 +299,7 @@ "Xopqkl": "จํานวน zap เริ่มต้นของคุณคือ {number} sats ค่าตัวอย่างคํานวณจากสิ่งนี้", "XrSk2j": "รับคืน", "YDURw6": "Service URL", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "เปิดใช้งาน reactions", "Z4BMCZ": "ใส่ pairing phrase", "ZKORll": "เปิดใช้งานทันที", @@ -381,6 +383,7 @@ "ieGrWo": "ติดตาม", "itPgxd": "โปรไฟล์", "izWS4J": "เลิกติดตาม", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} sat} other{{n} sats}}", "jAmfGl": "Your {site_name} subscription is expired", "jHa/ko": "Clean up your feed", @@ -442,6 +445,7 @@ "qz9fty": "Incorrect pin", "r3C4x/": "ซอฟต์แวร์", "r5srDR": "โปรดใส่รหัส wallet", + "rMgF34": "Back up now", "rT14Ow": "เพิ่มรีเลย์", "rbrahO": "ปิด", "rfuMjE": "(ค่าเริ่มต้น)", diff --git a/packages/app/src/translations/zh_CN.json b/packages/app/src/translations/zh_CN.json index 1b068b9ce..f5562ee22 100644 --- a/packages/app/src/translations/zh_CN.json +++ b/packages/app/src/translations/zh_CN.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "是否确定要从收藏中移除此条笔记?", "1R43+L": "输入 Nostr Wallet Connect 配置", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "已连接到:{node}🎉", "1nYUGC": "{n} 个关注", "1o2BgB": "检查签名", @@ -298,6 +299,7 @@ "Xopqkl": "你的默认打闪金额是 {number} 聪,示例值是以此计算的。", "XrSk2j": "兑现", "YDURw6": "服务网址", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "启用回应", "Z4BMCZ": "输入配对词句", "ZKORll": "立即激活", @@ -381,6 +383,7 @@ "ieGrWo": "关注", "itPgxd": "个人档案", "izWS4J": "取消关注", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n}聪} other{{n}聪}}", "jAmfGl": "你的 {site_name} 订阅已过期", "jHa/ko": "清理你的订阅", @@ -442,6 +445,7 @@ "qz9fty": "PIN 码不正确", "r3C4x/": "软件", "r5srDR": "输入钱包密码", + "rMgF34": "Back up now", "rT14Ow": "添加中继", "rbrahO": "关闭", "rfuMjE": "(默认)", diff --git a/packages/app/src/translations/zh_TW.json b/packages/app/src/translations/zh_TW.json index a66662e73..bdad21836 100644 --- a/packages/app/src/translations/zh_TW.json +++ b/packages/app/src/translations/zh_TW.json @@ -30,6 +30,7 @@ "1H4Keq": "{n} users", "1Mo59U": "是否確定要從收藏中移除此條筆記?", "1R43+L": "輸入 Nostr Wallet Connect 配置", + "1UWegE": "Be sure to back up your keys!", "1c4YST": "已連接到:{node} 🎉", "1nYUGC": "{n} 個關注", "1o2BgB": "檢查簽名", @@ -298,6 +299,7 @@ "Xopqkl": "你的默認打閃金額是 {number} 聰,示例值是以此計算的。", "XrSk2j": "兌現", "YDURw6": "服務 URL", + "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", "YXA3AH": "启用回應", "Z4BMCZ": "輸入配對詞句", "ZKORll": "立即激活", @@ -381,6 +383,7 @@ "ieGrWo": "關注", "itPgxd": "個人檔案", "izWS4J": "取消關注", + "j9xbzF": "Already backed up", "jA3OE/": "{n,plural,=1{{n} 聰} other{{n} 聰}}", "jAmfGl": "你的 {site_name} 訂閱已過期了", "jHa/ko": "清理你的訂閱", @@ -442,6 +445,7 @@ "qz9fty": "PIN 碼不正確", "r3C4x/": "軟件", "r5srDR": "輸入錢包密碼", + "rMgF34": "Back up now", "rT14Ow": "添加中繼器", "rbrahO": "關閉", "rfuMjE": "(默認)", From 68ebe5e7b125a71e81956244dee08750dab02b40 Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 23 Nov 2023 13:59:28 +0000 Subject: [PATCH 13/21] fix: notifications marker --- .prettierignore | 3 ++- packages/app/src/Login/LoginSession.ts | 5 ---- packages/app/src/Pages/Layout.css | 6 ++--- packages/app/src/Pages/Layout.tsx | 33 +++++++++++++++++++------- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/.prettierignore b/.prettierignore index 9b4632dac..312750b07 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,4 +4,5 @@ build/ .github/ transifex.yml dist/ -src-tauri/ \ No newline at end of file +src-tauri/ +target/ \ No newline at end of file diff --git a/packages/app/src/Login/LoginSession.ts b/packages/app/src/Login/LoginSession.ts index 0527cd62e..91172e193 100644 --- a/packages/app/src/Login/LoginSession.ts +++ b/packages/app/src/Login/LoginSession.ts @@ -95,11 +95,6 @@ export interface LoginSession { */ blocked: Newest>; - /** - * Latest notification - */ - latestNotification: number; - /** * Timestamp of last read notification */ diff --git a/packages/app/src/Pages/Layout.css b/packages/app/src/Pages/Layout.css index 64ccf63ce..780e78374 100644 --- a/packages/app/src/Pages/Layout.css +++ b/packages/app/src/Pages/Layout.css @@ -48,9 +48,9 @@ header { border-radius: 100%; width: 9px; height: 9px; - position: absolute; - top: 8px; - right: 8px; + position: relative; + top: -4px; + right: 7px; } @media (max-width: 520px) { diff --git a/packages/app/src/Pages/Layout.tsx b/packages/app/src/Pages/Layout.tsx index c71fd5028..468b3639e 100644 --- a/packages/app/src/Pages/Layout.tsx +++ b/packages/app/src/Pages/Layout.tsx @@ -1,5 +1,5 @@ import "./Layout.css"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useState, useSyncExternalStore } from "react"; import { Link, Outlet, useLocation, useNavigate } from "react-router-dom"; import { FormattedMessage } from "react-intl"; import { useUserProfile } from "@snort/system-react"; @@ -24,6 +24,7 @@ import { ProfileLink } from "@/Element/User/ProfileLink"; import SearchBox from "../Element/SearchBox"; import SnortApi from "@/External/SnortApi"; import useEventPublisher from "@/Hooks/useEventPublisher"; +import { Notifications } from "@/Cache"; export default function Layout() { const location = useLocation(); @@ -99,19 +100,13 @@ const AccountHeader = () => { } }); - const { publicKey, latestNotification, readNotifications, readonly } = useLogin(s => ({ + const { publicKey, readonly } = useLogin(s => ({ publicKey: s.publicKey, - latestNotification: s.latestNotification, - readNotifications: s.readNotifications, readonly: s.readonly, })); const profile = useUserProfile(publicKey); const { publisher } = useEventPublisher(); - const hasNotifications = useMemo( - () => latestNotification > readNotifications, - [latestNotification, readNotifications], - ); const unreadDms = useMemo(() => (publicKey ? 0 : 0), [publicKey]); async function goToNotifications() { @@ -166,7 +161,7 @@ const AccountHeader = () => { )} - {hasNotifications && } + @@ -200,3 +195,23 @@ function LogoHeader() { ); } + +function HasNotificationsMarker() { + const readNotifications = useLogin(s => s.readNotifications); + const notifications = useSyncExternalStore( + c => Notifications.hook(c, "*"), + () => Notifications.snapshot(), + ); + const latestNotification = useMemo( + () => notifications.reduce((acc, v) => (v.created_at > acc ? v.created_at : acc), 0), + [notifications], + ); + const hasNotifications = useMemo( + () => latestNotification * 1000 > readNotifications, + [notifications, readNotifications], + ); + + if (hasNotifications) { + return ; + } +} From e18500c80b91f402dc051e24443515fb75da7b7a Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 23 Nov 2023 14:37:45 +0000 Subject: [PATCH 14/21] chore: bump version --- packages/app/CHANGELOG.md | 49 +++++++++++++++++++++++++++++++++++++++ packages/app/package.json | 2 +- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 0c0e55fd5..b12d43cce 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,3 +1,52 @@ +# v0.1.23 + +## Added +- DeepL translate api (Automatic for PRO subscribers) +- Add nostr:nprofile1qqsydl97xpj74udw0qg5vkfyujyjxd3l706jd0t0w0turp93d0vvungfgfewr to contributors +- Proxy LN address type enabled on Nostr Address settings pages +- Infinite scrol on notifications page +- Default 0.5% ZapPool rate for Snort donation address +- Collect relay metrics in `@snort/system` for better relay selection algo in Outbox Model (NIP-65) +- New sign up / login flow! + - Topics / Mute words on sign up for easier onboarding +- Drag & Drop for uploads on note creator - nostr:nprofile1qqs8tchhwf5smv3r2g0vkswz58c837uu456x59m3dh380gtrhqzydeqz4wlka +- Mixin topics (hashtags) into timeline feed +- Language specific trending posts +- Show following info for hashtags +- Sync preferences to network (`NIP-78` support) +- Trending hashtags page +- Note creator hashtag input +- Top trending hashtags on note creator +- Social Graph - nostr:nprofile1qqsy2ga7trfetvd3j65m3jptqw9k39wtq2mg85xz2w542p5dhg06e5qpr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uh8am0r +- New users relay list based off "close" relays +- `NIP-96` support for nostr native image/file uploaders +- Write replies/reactions to `p` tagged users read relays (Outbox model) +- Sync joined public chats (`NIP-28`) using `PublicChatList` kind `10_005` + +## Changed +- Read/Write relays only on kind `10_002` (NIP-65) +- Removed `nostr.watch` code for adding new users to random relays +- Render kind `10_002` on profile relays tab +- `@snort/system` using eventemitter3 for triggering events +- Use latest `NIP-51` spec (Bookmarks/Interests/`NIP-28` PublicChatList) +- `nreq` support (Demo) +- Write profile/relays to blasters +- `@snort/system` automated outbox model (automatic fetching of relay metadata) + +## Fixes +- Upgrade ephermal connection to non-ephemeral +- Remove relay tag from zaps (Some zap services dont support it) +- Fix zap parsing for goals +- Remove extra chars from quoted events to fix loading (`'s` etc) +- CSS Fixes for profile card on light theme +- Zap counting on replacable events +- `NIP-28` chats loading +- Overflowing modal UI +- Live stream widget layout with long titles +- Notifications marker has returned from its long slumber + +--- + # v0.1.22 ## Fixes diff --git a/packages/app/package.json b/packages/app/package.json index 847d621fc..4e286c3cd 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@snort/app", - "version": "0.1.22", + "version": "0.1.23", "dependencies": { "@cashu/cashu-ts": "^0.6.1", "@lightninglabs/lnc-web": "^0.2.3-alpha", From b5606028e0c36b1bee66517cc0f062d86046a9b0 Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 23 Nov 2023 14:40:23 +0000 Subject: [PATCH 15/21] chore: Update translations --- packages/app/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index b12d43cce..6212ae159 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,6 +1,7 @@ # v0.1.23 ## Added + - DeepL translate api (Automatic for PRO subscribers) - Add nostr:nprofile1qqsydl97xpj74udw0qg5vkfyujyjxd3l706jd0t0w0turp93d0vvungfgfewr to contributors - Proxy LN address type enabled on Nostr Address settings pages @@ -24,6 +25,7 @@ - Sync joined public chats (`NIP-28`) using `PublicChatList` kind `10_005` ## Changed + - Read/Write relays only on kind `10_002` (NIP-65) - Removed `nostr.watch` code for adding new users to random relays - Render kind `10_002` on profile relays tab @@ -34,6 +36,7 @@ - `@snort/system` automated outbox model (automatic fetching of relay metadata) ## Fixes + - Upgrade ephermal connection to non-ephemeral - Remove relay tag from zaps (Some zap services dont support it) - Fix zap parsing for goals @@ -41,7 +44,7 @@ - CSS Fixes for profile card on light theme - Zap counting on replacable events - `NIP-28` chats loading -- Overflowing modal UI +- Overflowing modal UI - Live stream widget layout with long titles - Notifications marker has returned from its long slumber From 365031516d3d1c7ae75fa798c753ee48eb932990 Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 23 Nov 2023 14:41:19 +0000 Subject: [PATCH 16/21] fix: build --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e252ddce..8301fa8ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -75,7 +75,7 @@ jobs: run: |- git clone --depth 1 --branch ${{ github.ref_name }} https://git.v0l.io/Kieran/snort_android.git mkdir -p snort_android/app/src/main/assets/ - cp packages/app/build/* snort_android/app/src/main/assets/ + cp -r packages/app/build/* snort_android/app/src/main/assets/ - name: Build AAB working-directory: snort_android From edfe9c36970029bb7b073848fb6ee485f8288f92 Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 24 Nov 2023 10:39:15 +0000 Subject: [PATCH 17/21] fix: clear cache on install --- packages/app/src/service-worker.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/app/src/service-worker.ts b/packages/app/src/service-worker.ts index c3dc97f68..5376c200b 100644 --- a/packages/app/src/service-worker.ts +++ b/packages/app/src/service-worker.ts @@ -17,6 +17,21 @@ self.addEventListener("message", event => { self.skipWaiting(); } }); +self.addEventListener("install", event => { + // delete all cache on install + event.waitUntil( + caches.keys().then(cacheNames => { + return Promise.all( + cacheNames.map(cacheName => { + console.debug("Deleting cache: ", cacheName); + return caches.delete(cacheName); + }), + ); + }), + ); + // always skip waiting + self.skipWaiting(); +}); const enum PushType { Mention = 1, From e21e5499699f0111d013e78f35b01adcd3ca2bb7 Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 24 Nov 2023 10:41:04 +0000 Subject: [PATCH 18/21] chore: Update translations --- packages/app/src/translations/de_DE.json | 8 ++++---- packages/app/src/translations/hu_HU.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/app/src/translations/de_DE.json b/packages/app/src/translations/de_DE.json index 9acd2ff74..8f7c16618 100644 --- a/packages/app/src/translations/de_DE.json +++ b/packages/app/src/translations/de_DE.json @@ -30,7 +30,7 @@ "1H4Keq": "{n} Benutzer", "1Mo59U": "Bist du sicher, dass du diese Note aus deinen Lesezeichen entfernen möchtest?", "1R43+L": "Nostr Wallet Connect Konfiguration eingeben", - "1UWegE": "Be sure to back up your keys!", + "1UWegE": "Stelle sicher, dass du deine Schlüssel absicherst!", "1c4YST": "Verbunden mit: {node}🎉", "1nYUGC": "{n} Folgen", "1o2BgB": "Signaturen prüfen", @@ -299,7 +299,7 @@ "Xopqkl": "Dein standardmäßiger Zap-Betrag ist {number} sats, Beispielwerte werden daraus berechnet.", "XrSk2j": "Einlösen", "YDURw6": "URL des Dienstes", - "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", + "YR2I9M": "Keine Schlüssel, kein {app}. Es gibt keine Möglichkeit, sie zurückzusetzen, wenn du keine Sicherungskopie gemacht hast. Es dauert nur eine Minute.", "YXA3AH": "Reaktionen aktivieren", "Z4BMCZ": "Verbindungs-Passphrase eingeben", "ZKORll": "Jetzt aktivieren", @@ -383,7 +383,7 @@ "ieGrWo": "Folgen", "itPgxd": "Profil", "izWS4J": "Entfolgen", - "j9xbzF": "Already backed up", + "j9xbzF": "Bereits gesichert", "jA3OE/": "{n,plural,one {}=1{{n} sat} other{{n} sats}}", "jAmfGl": "Dein {site_name} Abonnement ist abgelaufen", "jHa/ko": "Räume deinen Feed auf", @@ -445,7 +445,7 @@ "qz9fty": "Falsche PIN", "r3C4x/": "Software", "r5srDR": "Wallet Passwort eingeben", - "rMgF34": "Back up now", + "rMgF34": "Jetzt sichern", "rT14Ow": "Relais hinzufügen", "rbrahO": "Schließen", "rfuMjE": "(Standard)", diff --git a/packages/app/src/translations/hu_HU.json b/packages/app/src/translations/hu_HU.json index deed22bfc..8c69a951e 100644 --- a/packages/app/src/translations/hu_HU.json +++ b/packages/app/src/translations/hu_HU.json @@ -30,7 +30,7 @@ "1H4Keq": "{n} felhasználó", "1Mo59U": "Biztos hogy a kedvencekből ezt a bejegyzést el akarod távolítani?", "1R43+L": "Írd be a Nostr Wallet Connect konfigurációt", - "1UWegE": "Be sure to back up your keys!", + "1UWegE": "A kulcsaidról, mindenképpen készíts biztonsági másolatot!", "1c4YST": "Kapcsolódás a: {node} 🎉", "1nYUGC": "{n} Követek", "1o2BgB": "Aláírások ellenörzése", @@ -299,7 +299,7 @@ "Xopqkl": "Az alapértelmezett zap összeg {number} sats, a példaértékek kiszámítása ebből történik.", "XrSk2j": "Beváltás", "YDURw6": "Szervíz cím", - "YR2I9M": "No keys, no {app}, There is no way to reset it if you don't back up. It only takes a minute.", + "YR2I9M": "Nincsenek kulcsok, nincs {app}, Nincs mód a visszaállításra, ha nem készítesz biztonsági mentést. Csak egy percet vesz igénybe.", "YXA3AH": "Reakciók engedélyezése", "Z4BMCZ": "Párosító kifejezés megadása", "ZKORll": "Aktiválás", @@ -383,7 +383,7 @@ "ieGrWo": "Követem", "itPgxd": "Profil", "izWS4J": "Követés visszavonása", - "j9xbzF": "Already backed up", + "j9xbzF": "Már kész a biztonsági mentés", "jA3OE/": "{n,plural,one {}=1{{n} sat} other{{n} sats}}", "jAmfGl": "{site_name}-előfizetése lejárt", "jHa/ko": "Tisztítsa meg a takarmányt", @@ -445,7 +445,7 @@ "qz9fty": "Helytelen Pin-kód", "r3C4x/": "Szoftver", "r5srDR": "Add meg a pénztárcád jelszavát", - "rMgF34": "Back up now", + "rMgF34": "Biztonsági mentés", "rT14Ow": "Csomópont hozzáadása", "rbrahO": "Bezárás", "rfuMjE": "(Alapértelmezett)", From 0f9f8ecb95cd598eef531188dfa82806d2f2327e Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 24 Nov 2023 12:09:34 +0000 Subject: [PATCH 19/21] fix: brave toString weirdness --- packages/system-react/src/useEventReactions.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/system-react/src/useEventReactions.tsx b/packages/system-react/src/useEventReactions.tsx index 95dc8c2c4..a47d864c9 100644 --- a/packages/system-react/src/useEventReactions.tsx +++ b/packages/system-react/src/useEventReactions.tsx @@ -21,9 +21,9 @@ export function useEventReactions(link: NostrLink, related: ReadonlyArray>, ); - const deletions = reactionKinds[EventKind.Deletion.toString()] ?? []; - const reactions = reactionKinds[EventKind.Reaction.toString()] ?? []; - const reposts = reactionKinds[EventKind.Repost.toString()] ?? []; + const deletions = reactionKinds[String(EventKind.Deletion)] ?? []; + const reactions = reactionKinds[String(EventKind.Reaction)] ?? []; + const reposts = reactionKinds[String(EventKind.Repost)] ?? []; const groupReactions = reactions?.reduce( (acc, reaction) => { @@ -35,7 +35,7 @@ export function useEventReactions(link: NostrLink, related: ReadonlyArray>, ); - const zaps = (reactionKinds[EventKind.ZapReceipt] ?? []) + const zaps = (reactionKinds[String(EventKind.ZapReceipt)] ?? []) .map(a => parseZap(a)) .filter(a => a.valid) .sort((a, b) => b.amount - a.amount); From b62519db3137eb4780e4870d093227fd6371274d Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 24 Nov 2023 17:04:04 +0000 Subject: [PATCH 20/21] chore: drop twitter embed --- packages/app/package.json | 1 - packages/app/src/Const.ts | 5 ----- packages/app/src/Element/HyperText.tsx | 13 ++----------- packages/app/src/index.css | 18 ------------------ yarn.lock | 20 -------------------- 5 files changed, 2 insertions(+), 55 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 4e286c3cd..9c291ae89 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -35,7 +35,6 @@ "react-router-dom": "^6.5.0", "react-tag-input-component": "^2.0.2", "react-textarea-autosize": "^8.4.0", - "react-twitter-embed": "^4.0.4", "recharts": "^2.8.0", "three": "^0.157.0", "use-long-press": "^3.2.0", diff --git a/packages/app/src/Const.ts b/packages/app/src/Const.ts index 904ccc345..5a87d6f3b 100644 --- a/packages/app/src/Const.ts +++ b/packages/app/src/Const.ts @@ -93,11 +93,6 @@ export const InvoiceRegex = /(lnbc\w+)/i; export const YoutubeUrlRegex = /(?:https?:\/\/)?(?:www|m\.)?(?:youtu\.be\/|youtube\.com\/(?:live\/|shorts\/|embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})/; -/** - * Tweet Regex - */ -export const TweetUrlRegex = /https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)/; - /** * Hashtag regex */ diff --git a/packages/app/src/Element/HyperText.tsx b/packages/app/src/Element/HyperText.tsx index 61e12b520..bbe4a67b5 100644 --- a/packages/app/src/Element/HyperText.tsx +++ b/packages/app/src/Element/HyperText.tsx @@ -1,8 +1,5 @@ -import { TwitterTweetEmbed } from "react-twitter-embed"; - import { YoutubeUrlRegex, - TweetUrlRegex, TidalRegex, SoundCloudRegex, MixCloudRegex, @@ -37,7 +34,6 @@ export default function HyperText({ link, depth, showLinkPreview, children }: Hy try { const url = new URL(a); const youtubeId = YoutubeUrlRegex.test(a) && RegExp.$1; - const tweetId = TweetUrlRegex.test(a) && RegExp.$2; const tidalId = TidalRegex.test(a) && RegExp.$1; const soundcloundId = SoundCloudRegex.test(a) && RegExp.$1; const mixcloudId = MixCloudRegex.test(a) && RegExp.$1; @@ -46,13 +42,8 @@ export default function HyperText({ link, depth, showLinkPreview, children }: Hy const isAppleMusicLink = AppleMusicRegex.test(a); const isNostrNestsLink = NostrNestsRegex.test(a); const isWavlakeLink = WavlakeRegex.test(a); - if (tweetId) { - return ( -
- -
- ); - } else if (youtubeId) { + + if (youtubeId) { return (