From 4e4039ea7abea6ee6b4ef2a1543f7e327eb5e2e0 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Tue, 5 Mar 2024 15:29:01 -0800 Subject: [PATCH] Re-work address --- src/util/Address.ts | 136 ++++++++++++++++++-------------------------- src/util/Events.ts | 4 +- src/util/Router.ts | 9 ++- src/util/Tags.ts | 26 +++++++-- 4 files changed, 85 insertions(+), 90 deletions(-) diff --git a/src/util/Address.ts b/src/util/Address.ts index d4b3f5c..a56d31d 100644 --- a/src/util/Address.ts +++ b/src/util/Address.ts @@ -1,85 +1,61 @@ import type {UnsignedEvent} from 'nostr-tools' import {nip19} from 'nostr-tools' import {GROUP_DEFINITION, COMMUNITY_DEFINITION} from './Kinds' -import {Tags} from './Tags' -export const isGroupAddress = (a: string) => a.startsWith(`${GROUP_DEFINITION}:`) - -export const isCommunityAddress = (a: string) => a.startsWith(`${COMMUNITY_DEFINITION}:`) - -export const isContextAddress = (a: string) => isCommunityAddress(a) || isGroupAddress(a) - -export class Address { - readonly kind: number - - constructor( - kind: string | number, - readonly pubkey: string, - readonly identifier: string, - readonly relays: string[], - ) { - this.kind = parseInt(kind as string) - this.identifier = identifier || "" - } - - static getKind = (a: string) => Address.fromRaw(a, []).kind - - static getPubkey = (a: string) => Address.fromRaw(a, []).pubkey - - static getIdentifier = (a: string) => Address.fromRaw(a, []).identifier - - static fromEvent = (e: UnsignedEvent, relays: string[]) => - new Address(e.kind, e.pubkey, Tags.fromEvent(e).get("d")?.value() || "", relays) - - static fromRaw = (a: string, relays: string[]) => { - const [kind, pubkey, identifier] = a.split(":") - - return new Address(kind, pubkey, identifier, relays) - } - - static fromTag = (tag: string[]) => { - const [a, hint] = tag.slice(1) - const relays = hint ? [hint] : [] - - return this.fromRaw(a, relays) - } - - static fromNaddr = (naddr: string) => { - let type - let data = {} as any - try { - ({type, data} = nip19.decode(naddr) as { - type: "naddr" - data: any - }) - } catch (e) { - // pass - } - - if (type !== "naddr") { - throw new Error(`Invalid naddr ${naddr}`) - } - - return new Address(data.kind, data.pubkey, data.identifier, data.relays) - } - - asRaw = () => [this.kind, this.pubkey, this.identifier].join(":") - - asTag = (mark?: string) => { - const tag = ["a", this.asRaw(), this.relays[0] || ""] - - if (mark) { - tag.push(mark) - } - - return tag - } - - asNaddr = () => nip19.naddrEncode(this) - - asFilter = () => ({ - kinds: [this.kind], - authors: [this.pubkey], - "#d": [this.identifier], - }) +export type Address = { + kind: number, + pubkey: string + identifier: string + relays: string[] } + +// Plain text format + +export const decodeAddress = (a: string, relays: string[] = []): Address => { + const [kind, pubkey, identifier = ""] = a.split(":") + + return {kind: parseInt(kind), pubkey, identifier, relays} +} + +export const encodeAddress = (a: Address) => [a.kind, a.pubkey, a.identifier].join(":") + +// Naddr encoding + +export const addressFromNaddr = (naddr: string): Address => { + let type + let data = {} as any + try { + ({type, data} = nip19.decode(naddr) as { + type: "naddr" + data: any + }) + } catch (e) { + // pass + } + + if (type !== "naddr") { + throw new Error(`Invalid naddr ${naddr}`) + } + + return data +} + +export const addressToNaddr = (a: Address): string => nip19.naddrEncode(a) + +// Get from event, encode to filter + +export const getIdentifier = (e: UnsignedEvent) => e.tags.find(t => t[0] === "d")?.[1] || "" + +export const addressFromEvent = (e: UnsignedEvent, relays: string[] = []) => + ({kind: e.kind, pubkey: e.pubkey, identifier: getIdentifier(e), relays}) + +export const addressToFilter = (a: Address) => + ({kinds: [a.kind], authors: [a.pubkey], "#d": [a.identifier]}) + +// Utils + +export const isGroupAddress = (a: Address) => a.kind === GROUP_DEFINITION + +export const isCommunityAddress = (a: Address) => a.kind === COMMUNITY_DEFINITION + +export const isContextAddress = (a: Address) => [GROUP_DEFINITION, COMMUNITY_DEFINITION].includes(a.kind) diff --git a/src/util/Events.ts b/src/util/Events.ts index 2c33cbb..12b3bce 100644 --- a/src/util/Events.ts +++ b/src/util/Events.ts @@ -2,7 +2,7 @@ import type {Event, EventTemplate, UnsignedEvent} from 'nostr-tools' import {verifyEvent, getEventHash} from 'nostr-tools' import {cached} from "./LRUCache" import {now} from './Tools' -import {Address} from './Address' +import {addressFromEvent, encodeAddress} from './Address' import {isEphemeralKind, isReplaceableKind, isPlainReplaceableKind, isParameterizedReplaceableKind} from './Kinds' export type Rumor = Pick @@ -43,7 +43,7 @@ export const hasValidSignature = cached({ }, }) -export const getAddress = (e: UnsignedEvent) => Address.fromEvent(e, []).asRaw() +export const getAddress = (e: UnsignedEvent) => encodeAddress(addressFromEvent(e)) export const getIdOrAddress = (e: Rumor) => isReplaceable(e) ? getAddress(e) : e.id diff --git a/src/util/Router.ts b/src/util/Router.ts index 482276a..90893d8 100644 --- a/src/util/Router.ts +++ b/src/util/Router.ts @@ -3,7 +3,12 @@ import type {Rumor} from './Events' import {getAddress, isReplaceable} from './Events' import {Tag, Tags} from './Tags' import {first, uniq, shuffle} from './Tools' -import {Address, isGroupAddress, isCommunityAddress} from './Address' +import {GROUP_DEFINITION, COMMUNITY_DEFINITION} from './Kinds' +import {addressFromEvent, decodeAddress} from './Address' + +const isGroupAddress = (a: string) => decodeAddress(a).kind === GROUP_DEFINITION + +const isCommunityAddress = (a: string) => decodeAddress(a).kind === COMMUNITY_DEFINITION export enum RelayMode { Inbox = "inbox", @@ -205,7 +210,7 @@ export class Router { } address = (event: UnsignedEvent) => - Address.fromEvent(event, this.Event(event).limit(3).getUrls()) + addressFromEvent(event, this.Event(event).limit(3).getUrls()) } // Router Scenario diff --git a/src/util/Tags.ts b/src/util/Tags.ts index e4aef88..89b7e7b 100644 --- a/src/util/Tags.ts +++ b/src/util/Tags.ts @@ -3,13 +3,17 @@ import {Fluent} from './Fluent' import type {OmitStatics} from './Tools' import {last} from './Tools' import {isShareableRelayUrl} from './Relays' -import {isCommunityAddress, isGroupAddress, isContextAddress} from './Address' +import type {Address} from './Address' +import {encodeAddress, decodeAddress} from './Address' +import {GROUP_DEFINITION, COMMUNITY_DEFINITION} from './Kinds' export class Tag extends (Fluent as OmitStatics, 'from'>) { static from(xs: Iterable) { return new Tag(Array.from(xs)) } + static fromAddress = (a: Address) => new Tag(["a", encodeAddress(a), a.relays[0] || ""]) + valueOf = () => this.xs key = () => this.xs[0] @@ -25,6 +29,16 @@ export class Tag extends (Fluent as OmitStatics, ' setValue = (v: string) => this.set(1, v) setMark = (m: string) => this.xs.length > 2 ? this.set(this.xs.length - 2, m) : this.append(m) + + asAddress = () => decodeAddress(this.value()) + + isAddress = (kind?: number) => this.key() === "a" && this.value()?.startsWith(`${kind}:`) + + isGroup = () => this.isAddress(GROUP_DEFINITION) + + isCommunity = () => this.isAddress(COMMUNITY_DEFINITION) + + isContext = () => this.isAddress(GROUP_DEFINITION) || this.isAddress(COMMUNITY_DEFINITION) } export class Tags extends (Fluent as OmitStatics, 'from'>) { @@ -70,7 +84,7 @@ export class Tags extends (Fluent as OmitStatics, 'from' topics = () => this.whereKey("t").values().map((t: string) => t.replace(/^#/, "")) ancestors = () => { - const tags = this.filter(t => ["a", "e", "q"].includes(t.key()) && !isContextAddress(t.value())) + const tags = this.filter(t => ["a", "e", "q"].includes(t.key()) && !t.isContext()) const roots: string[][] = [] const replies: string[][] = [] const mentions: string[][] = [] @@ -129,11 +143,11 @@ export class Tags extends (Fluent as OmitStatics, 'from' return parents.get("e") || parents.get("a") } - groups = () => this.whereKey("a").filter(t => isGroupAddress(t.value())) + groups = () => this.whereKey("a").filter(t => t.isGroup()) - communities = () => this.whereKey("a").filter(t => isCommunityAddress(t.value())) + communities = () => this.whereKey("a").filter(t => t.isCommunity()) - context = () => this.whereKey("a").filter(t => isContextAddress(t.value())) + context = () => this.whereKey("a").filter(t => t.isContext()) asObject = () => { const result: Record = {} @@ -167,7 +181,7 @@ export class Tags extends (Fluent as OmitStatics, 'from' addContext = (addresses: string[]) => this.concat(addresses.map(a => Tag.from(["a", a]))) - removeContext = () => this.reject(t => t.key() === "a" && isContextAddress(t.value())) + removeContext = () => this.reject(t => t.isContext()) setContext = (addresses: string[]) => this.removeContext().addContext(addresses)