Remove tsc-multi, re-install gts, apply autoformatting and linting
This commit is contained in:
@@ -11,26 +11,25 @@
|
||||
"files": [
|
||||
"build"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.4.0"
|
||||
},
|
||||
"types": "./build/src/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./build/src/index.d.ts",
|
||||
"import": "./build/src/index.mjs",
|
||||
"require": "./build/src/index.cjs"
|
||||
"import": "./build/src/index.js",
|
||||
"require": "./build/src/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"pub": "npm run lint && npm run build && npm publish",
|
||||
"build": "gts clean && tsc-multi",
|
||||
"build": "gts clean && tsc",
|
||||
"lint": "gts lint",
|
||||
"fix": "gts fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gts": "^5.0.1",
|
||||
"tsc-multi": "^1.1.0",
|
||||
"typescript": "~5.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/ws": "^8.5.13",
|
||||
"@welshman/lib": "~0.0.33",
|
||||
"nostr-tools": "^2.7.2"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {nip19} from "nostr-tools"
|
||||
import {decode, naddrEncode} from "nostr-tools/nip19"
|
||||
|
||||
// Define this locally to avoid circular dependencies
|
||||
type AddressableEvent = {
|
||||
@@ -12,7 +12,7 @@ export class Address {
|
||||
readonly kind: number,
|
||||
readonly pubkey: string,
|
||||
readonly identifier: string,
|
||||
readonly relays: string[] = []
|
||||
readonly relays: string[] = [],
|
||||
) {}
|
||||
|
||||
static isAddress(address: string) {
|
||||
@@ -26,22 +26,21 @@ export class Address {
|
||||
}
|
||||
|
||||
static fromNaddr(naddr: string) {
|
||||
let type
|
||||
let data = {} as any
|
||||
let decoded: any
|
||||
|
||||
try {
|
||||
({type, data} = nip19.decode(naddr) as {
|
||||
type: "naddr"
|
||||
data: any
|
||||
})
|
||||
decoded = decode(naddr)
|
||||
} catch (e) {
|
||||
// pass
|
||||
}
|
||||
|
||||
if (type !== "naddr") {
|
||||
if (decoded?.type !== "naddr") {
|
||||
throw new Error(`Invalid naddr ${naddr}`)
|
||||
}
|
||||
|
||||
return new Address(data.kind, data.pubkey, data.identifier, data.relays)
|
||||
const {kind, pubkey, identifier, relays} = decoded.data
|
||||
|
||||
return new Address(kind, pubkey, identifier, relays)
|
||||
}
|
||||
|
||||
static fromEvent(event: AddressableEvent, relays: string[] = []) {
|
||||
@@ -52,7 +51,7 @@ export class Address {
|
||||
|
||||
toString = () => [this.kind, this.pubkey, this.identifier].join(":")
|
||||
|
||||
toNaddr = () => nip19.naddrEncode(this)
|
||||
toNaddr = () => naddrEncode(this)
|
||||
}
|
||||
|
||||
// Utils
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type {EventContent, TrustedEvent, EventTemplate} from './Events'
|
||||
import type {EventContent, TrustedEvent, EventTemplate} from "./Events.js"
|
||||
|
||||
export type Encrypt = (x: string) => Promise<string>
|
||||
|
||||
@@ -27,7 +27,10 @@ export class Encryptable<T extends EventTemplate> {
|
||||
* const eventTemplate = await encryptable.reconcile(myEncryptFunction)
|
||||
* ```
|
||||
*/
|
||||
constructor(readonly event: Partial<T>, readonly updates: EncryptableUpdates) {}
|
||||
constructor(
|
||||
readonly event: Partial<T>,
|
||||
readonly updates: EncryptableUpdates,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Encrypts plaintext updates and merges them into the event template.
|
||||
@@ -49,7 +52,7 @@ export class Encryptable<T extends EventTemplate> {
|
||||
tag[1] = await encrypt(tag[1])
|
||||
|
||||
return tag
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+29
-21
@@ -1,8 +1,13 @@
|
||||
import {verifiedSymbol, getEventHash, verifyEvent} from 'nostr-tools'
|
||||
import {cached, pick, now} from '@welshman/lib'
|
||||
import {getAncestorTagValues} from './Tags'
|
||||
import {getAddress} from './Address'
|
||||
import {isEphemeralKind, isReplaceableKind, isPlainReplaceableKind, isParameterizedReplaceableKind} from './Kinds'
|
||||
import {verifiedSymbol, getEventHash, verifyEvent} from "nostr-tools/pure"
|
||||
import {cached, pick, now} from "@welshman/lib"
|
||||
import {getAncestorTagValues} from "./Tags.js"
|
||||
import {getAddress} from "./Address.js"
|
||||
import {
|
||||
isEphemeralKind,
|
||||
isReplaceableKind,
|
||||
isPlainReplaceableKind,
|
||||
isParameterizedReplaceableKind,
|
||||
} from "./Kinds.js"
|
||||
|
||||
export type EventContent = {
|
||||
tags: string[][]
|
||||
@@ -46,8 +51,10 @@ export type CreateEventOpts = {
|
||||
created_at?: number
|
||||
}
|
||||
|
||||
export const createEvent = (kind: number, {content = "", tags = [], created_at = now()}: CreateEventOpts) =>
|
||||
({kind, content, tags, created_at})
|
||||
export const createEvent = (
|
||||
kind: number,
|
||||
{content = "", tags = [], created_at = now()}: CreateEventOpts,
|
||||
) => ({kind, content, tags, created_at})
|
||||
|
||||
export const isEventTemplate = (e: EventTemplate): e is EventTemplate =>
|
||||
Boolean(typeof e.kind === "number" && Array.isArray(e.tags) && typeof e.content === "string")
|
||||
@@ -58,8 +65,7 @@ export const isStampedEvent = (e: StampedEvent): e is StampedEvent =>
|
||||
export const isOwnedEvent = (e: OwnedEvent): e is OwnedEvent =>
|
||||
Boolean(isStampedEvent(e) && e.pubkey)
|
||||
|
||||
export const isHashedEvent = (e: HashedEvent): e is HashedEvent =>
|
||||
Boolean(isOwnedEvent(e) && e.id)
|
||||
export const isHashedEvent = (e: HashedEvent): e is HashedEvent => Boolean(isOwnedEvent(e) && e.id)
|
||||
|
||||
export const isSignedEvent = (e: TrustedEvent): e is SignedEvent =>
|
||||
Boolean(isHashedEvent(e) && e.sig)
|
||||
@@ -71,25 +77,25 @@ export const isTrustedEvent = (e: TrustedEvent): e is TrustedEvent =>
|
||||
isSignedEvent(e) || isUnwrappedEvent(e)
|
||||
|
||||
export const asEventTemplate = (e: EventTemplate): EventTemplate =>
|
||||
pick(['kind', 'tags', 'content'], e)
|
||||
pick(["kind", "tags", "content"], e)
|
||||
|
||||
export const asStampedEvent = (e: StampedEvent): StampedEvent =>
|
||||
pick(['kind', 'tags', 'content', 'created_at'], e)
|
||||
pick(["kind", "tags", "content", "created_at"], e)
|
||||
|
||||
export const asOwnedEvent = (e: OwnedEvent): OwnedEvent =>
|
||||
pick(['kind', 'tags', 'content', 'created_at', 'pubkey'], e)
|
||||
pick(["kind", "tags", "content", "created_at", "pubkey"], e)
|
||||
|
||||
export const asHashedEvent = (e: HashedEvent): HashedEvent =>
|
||||
pick(['kind', 'tags', 'content', 'created_at', 'pubkey', 'id'], e)
|
||||
pick(["kind", "tags", "content", "created_at", "pubkey", "id"], e)
|
||||
|
||||
export const asSignedEvent = (e: SignedEvent): SignedEvent =>
|
||||
pick(['kind', 'tags', 'content', 'created_at', 'pubkey', 'id', 'sig'], e)
|
||||
pick(["kind", "tags", "content", "created_at", "pubkey", "id", "sig"], e)
|
||||
|
||||
export const asUnwrappedEvent = (e: UnwrappedEvent): UnwrappedEvent =>
|
||||
pick(['kind', 'tags', 'content', 'created_at', 'pubkey', 'id', 'wrap'], e)
|
||||
pick(["kind", "tags", "content", "created_at", "pubkey", "id", "wrap"], e)
|
||||
|
||||
export const asTrustedEvent = (e: TrustedEvent): TrustedEvent =>
|
||||
pick(['kind', 'tags', 'content', 'created_at', 'pubkey', 'id', 'sig', 'wrap'], e)
|
||||
pick(["kind", "tags", "content", "created_at", "pubkey", "id", "sig", "wrap"], e)
|
||||
|
||||
const _hasValidSignature = cached<string, boolean, [SignedEvent]>({
|
||||
maxSize: 10000,
|
||||
@@ -97,7 +103,7 @@ const _hasValidSignature = cached<string, boolean, [SignedEvent]>({
|
||||
try {
|
||||
return [getEventHash(e), e.sig].join(":")
|
||||
} catch (err) {
|
||||
return 'invalid'
|
||||
return "invalid"
|
||||
}
|
||||
},
|
||||
getValue: ([e]: [SignedEvent]) => {
|
||||
@@ -113,11 +119,12 @@ const _hasValidSignature = cached<string, boolean, [SignedEvent]>({
|
||||
|
||||
export const hasValidSignature = (e: SignedEvent) => e[verifiedSymbol] || _hasValidSignature(e)
|
||||
|
||||
export const getIdentifier = (e: EventTemplate) => e.tags.find(t => t[0] === 'd')?.[1]
|
||||
export const getIdentifier = (e: EventTemplate) => e.tags.find(t => t[0] === "d")?.[1]
|
||||
|
||||
export const getIdOrAddress = (e: HashedEvent) => isReplaceable(e) ? getAddress(e) : e.id
|
||||
export const getIdOrAddress = (e: HashedEvent) => (isReplaceable(e) ? getAddress(e) : e.id)
|
||||
|
||||
export const getIdAndAddress = (e: HashedEvent) => isReplaceable(e) ? [e.id, getAddress(e)] : [e.id]
|
||||
export const getIdAndAddress = (e: HashedEvent) =>
|
||||
isReplaceable(e) ? [e.id, getAddress(e)] : [e.id]
|
||||
|
||||
export const isEphemeral = (e: EventTemplate) => isEphemeralKind(e.kind)
|
||||
|
||||
@@ -125,7 +132,8 @@ export const isReplaceable = (e: EventTemplate) => isReplaceableKind(e.kind)
|
||||
|
||||
export const isPlainReplaceable = (e: EventTemplate) => isPlainReplaceableKind(e.kind)
|
||||
|
||||
export const isParameterizedReplaceable = (e: EventTemplate) => isParameterizedReplaceableKind(e.kind)
|
||||
export const isParameterizedReplaceable = (e: EventTemplate) =>
|
||||
isParameterizedReplaceableKind(e.kind)
|
||||
|
||||
export const isChildOf = (child: EventContent, parent: HashedEvent) => {
|
||||
const {roots, replies} = getAncestorTagValues(child.tags)
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
import {matchFilter as nostrToolsMatchFilter} from 'nostr-tools'
|
||||
import {without, uniqBy, prop, mapVals, shuffle, avg, hash, groupBy, randomId, uniq} from '@welshman/lib'
|
||||
import type {HashedEvent, TrustedEvent, SignedEvent} from './Events'
|
||||
import {isReplaceableKind} from './Kinds'
|
||||
import {Address, getAddress} from './Address'
|
||||
import {matchFilter as nostrToolsMatchFilter} from "nostr-tools/filter"
|
||||
import {
|
||||
without,
|
||||
uniqBy,
|
||||
prop,
|
||||
mapVals,
|
||||
shuffle,
|
||||
avg,
|
||||
hash,
|
||||
groupBy,
|
||||
randomId,
|
||||
uniq,
|
||||
} from "@welshman/lib"
|
||||
import type {HashedEvent, TrustedEvent, SignedEvent} from "./Events.js"
|
||||
import {isReplaceableKind} from "./Kinds.js"
|
||||
import {Address, getAddress} from "./Address.js"
|
||||
|
||||
export const EPOCH = 1609459200
|
||||
|
||||
@@ -58,12 +69,12 @@ export const getFilterId = (filter: Filter) => {
|
||||
const parts = []
|
||||
for (const k of keys) {
|
||||
const v = filter[k as keyof Filter]
|
||||
const s = Array.isArray(v) ? v.join(',') : v
|
||||
const s = Array.isArray(v) ? v.join(",") : v
|
||||
|
||||
parts.push([k, s].join(':'))
|
||||
parts.push([k, s].join(":"))
|
||||
}
|
||||
|
||||
return hash(parts.join('|'))
|
||||
return hash(parts.join("|"))
|
||||
}
|
||||
|
||||
export const calculateFilterGroup = ({since, until, limit, search, ...filter}: Filter) => {
|
||||
@@ -107,13 +118,13 @@ export const intersectFilters = (groups: Filter[][]) => {
|
||||
const f3: Filter = {}
|
||||
|
||||
for (const k of uniq([...Object.keys(f1), ...Object.keys(f2)]) as (keyof Filter)[]) {
|
||||
if (k === 'since' || k === 'limit') {
|
||||
if (k === "since" || k === "limit") {
|
||||
f3[k] = Math.max(f1[k] || 0, f2[k] || 0)
|
||||
} else if (k === 'until') {
|
||||
} else if (k === "until") {
|
||||
f3[k] = Math.min(f1[k] || f2[k] || 0, f2[k] || f1[k] || 0)
|
||||
} else if (k === 'search') {
|
||||
} else if (k === "search") {
|
||||
if (f1[k] && f2[k] && f1[k] !== f2[k]) {
|
||||
f3[k] = [f1[k], f2[k]].join(' ')
|
||||
f3[k] = [f1[k], f2[k]].join(" ")
|
||||
} else {
|
||||
f3[k] = f1[k] || f2[k]
|
||||
}
|
||||
@@ -186,7 +197,6 @@ export const getReplyFilters = (events: TrustedEvent[], filter: Filter = {}) =>
|
||||
return filters
|
||||
}
|
||||
|
||||
|
||||
export const addRepostFilters = (filters: Filter[]) =>
|
||||
filters.flatMap(original => {
|
||||
const filterChunk = [original]
|
||||
@@ -239,6 +249,9 @@ export const getFilterResultCardinality = (filter: Filter) => {
|
||||
}
|
||||
|
||||
export const trimFilter = (filter: Filter): Filter =>
|
||||
mapVals(v => Array.isArray(v) && v.length > 1000 ? shuffle(v as string[]).slice(0, 1000) : v, filter) as Filter
|
||||
mapVals(
|
||||
v => (Array.isArray(v) && v.length > 1000 ? shuffle(v as string[]).slice(0, 1000) : v),
|
||||
filter,
|
||||
) as Filter
|
||||
|
||||
export const trimFilters = (filters: Filter[]) => filters.map(trimFilter)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {fromPairs, last, first, parseJson} from "@welshman/lib"
|
||||
import {getAddress} from "./Address"
|
||||
import {getAddressTags, getKindTagValues} from "./Tags"
|
||||
import type {TrustedEvent} from "./Events"
|
||||
import {getAddress} from "./Address.js"
|
||||
import {getAddressTags, getKindTagValues} from "./Tags.js"
|
||||
import type {TrustedEvent} from "./Events.js"
|
||||
|
||||
export type Handler = {
|
||||
kind: number
|
||||
@@ -32,8 +32,12 @@ export const readHandlers = (event: TrustedEvent) => {
|
||||
return []
|
||||
}
|
||||
|
||||
return getKindTagValues(event.tags)
|
||||
.map(kind => ({...normalizedMeta, kind, identifier, event})) as Handler[]
|
||||
return getKindTagValues(event.tags).map(kind => ({
|
||||
...normalizedMeta,
|
||||
kind,
|
||||
identifier,
|
||||
event,
|
||||
})) as Handler[]
|
||||
}
|
||||
|
||||
export const getHandlerKey = (handler: Handler) => `${handler.kind}:${getAddress(handler.event)}`
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import {kinds} from 'nostr-tools'
|
||||
import {between} from '@welshman/lib'
|
||||
import {
|
||||
isRegularKind,
|
||||
isEphemeralKind,
|
||||
isReplaceableKind as isPlainReplaceableKind,
|
||||
isParameterizedReplaceableKind,
|
||||
} from "nostr-tools/kinds"
|
||||
import {between} from "@welshman/lib"
|
||||
|
||||
export {isRegularKind, isEphemeralKind, isPlainReplaceableKind, isParameterizedReplaceableKind}
|
||||
|
||||
export const isRegularKind = kinds.isRegularKind
|
||||
export const isEphemeralKind = kinds.isEphemeralKind
|
||||
export const isPlainReplaceableKind = kinds.isReplaceableKind
|
||||
export const isParameterizedReplaceableKind = kinds.isParameterizedReplaceableKind
|
||||
export const isReplaceableKind = (kind: number) =>
|
||||
isPlainReplaceableKind(kind) || isParameterizedReplaceableKind(kind)
|
||||
|
||||
export const isDVMKind = (kind: number) => between([4999, 7001], kind)
|
||||
|
||||
export const PROFILE = 0
|
||||
@@ -162,4 +166,3 @@ export const GROUP_ADMINS = 39001
|
||||
export const DEPRECATED_RELAY_RECOMMENDATION = 2
|
||||
export const DEPRECATED_DIRECT_MESSAGE = 4
|
||||
export const DEPRECATED_NAMED_GENERIC = 30001
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export const fromNostrURI = (s: string) => s.replace(/^nostr:\/?\/?/, "")
|
||||
|
||||
export const toNostrURI = (s: string) => s.startsWith('nostr:') ? s : `nostr:${s}`
|
||||
export const toNostrURI = (s: string) => (s.startsWith("nostr:") ? s : `nostr:${s}`)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {parseJson, nthEq} from "@welshman/lib"
|
||||
import {Address} from "./Address"
|
||||
import {uniqTags} from "./Tags"
|
||||
import {isShareableRelayUrl} from "./Relay"
|
||||
import {Encryptable, DecryptedEvent} from "./Encryptable"
|
||||
import type {EncryptableUpdates} from "./Encryptable"
|
||||
import {Address} from "./Address.js"
|
||||
import {uniqTags} from "./Tags.js"
|
||||
import {isShareableRelayUrl} from "./Relay.js"
|
||||
import {Encryptable, DecryptedEvent} from "./Encryptable.js"
|
||||
import type {EncryptableUpdates} from "./Encryptable.js"
|
||||
|
||||
export type ListParams = {
|
||||
kind: number
|
||||
@@ -19,8 +19,11 @@ export type PublishedList = Omit<List, "event"> & {
|
||||
event: DecryptedEvent
|
||||
}
|
||||
|
||||
export const makeList = (list: ListParams & Partial<List>): List =>
|
||||
({publicTags: [], privateTags: [], ...list})
|
||||
export const makeList = (list: ListParams & Partial<List>): List => ({
|
||||
publicTags: [],
|
||||
privateTags: [],
|
||||
...list,
|
||||
})
|
||||
|
||||
const isValidTag = (tag: string[]) => {
|
||||
if (tag[0] === "p") return tag[1]?.length === 64
|
||||
@@ -41,8 +44,10 @@ export const readList = (event: DecryptedEvent): PublishedList => {
|
||||
return {event, kind: event.kind, publicTags, privateTags}
|
||||
}
|
||||
|
||||
export const getListTags = (list: List | undefined) =>
|
||||
[...list?.publicTags || [], ...list?.privateTags || []]
|
||||
export const getListTags = (list: List | undefined) => [
|
||||
...(list?.publicTags || []),
|
||||
...(list?.privateTags || []),
|
||||
]
|
||||
|
||||
export const removeFromListByPredicate = (list: List, pred: (t: string[]) => boolean) => {
|
||||
const plaintext: EncryptableUpdates = {}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {nip19} from "nostr-tools"
|
||||
import {npubEncode} from "nostr-tools/nip19"
|
||||
import {ellipsize, parseJson} from "@welshman/lib"
|
||||
import {TrustedEvent} from "./Events"
|
||||
import {getLnUrl} from './Zaps'
|
||||
import {PROFILE} from "./Kinds"
|
||||
import {TrustedEvent} from "./Events.js"
|
||||
import {getLnUrl} from "./Zaps.js"
|
||||
import {PROFILE} from "./Kinds.js"
|
||||
|
||||
export type Profile = {
|
||||
name?: string
|
||||
@@ -27,13 +27,15 @@ export const isPublishedProfile = (profile: Profile): profile is PublishedProfil
|
||||
|
||||
export const makeProfile = (profile: Partial<Profile> = {}): Profile => {
|
||||
const address = profile.lud06 || profile.lud16
|
||||
const lnurl = typeof address === 'string' ? getLnUrl(address) : null
|
||||
const lnurl = typeof address === "string" ? getLnUrl(address) : null
|
||||
|
||||
return lnurl ? {lnurl, ...profile} : profile
|
||||
}
|
||||
|
||||
export const readProfile = (event: TrustedEvent): PublishedProfile =>
|
||||
({...makeProfile(parseJson(event.content) || {}), event})
|
||||
export const readProfile = (event: TrustedEvent): PublishedProfile => ({
|
||||
...makeProfile(parseJson(event.content) || {}),
|
||||
event,
|
||||
})
|
||||
|
||||
export const createProfile = ({event, ...profile}: Profile) => ({
|
||||
kind: PROFILE,
|
||||
@@ -47,7 +49,7 @@ export const editProfile = ({event, ...profile}: PublishedProfile) => ({
|
||||
})
|
||||
|
||||
export const displayPubkey = (pubkey: string) => {
|
||||
const d = nip19.npubEncode(pubkey)
|
||||
const d = npubEncode(pubkey)
|
||||
|
||||
return d.slice(0, 8) + "…" + d.slice(-5)
|
||||
}
|
||||
|
||||
+32
-28
@@ -1,8 +1,8 @@
|
||||
import {last, Emitter, normalizeUrl, sleep, stripProtocol} from '@welshman/lib'
|
||||
import {matchFilters} from './Filters'
|
||||
import type {Repository} from './Repository'
|
||||
import type {Filter} from './Filters'
|
||||
import type {HashedEvent, TrustedEvent} from './Events'
|
||||
import {last, Emitter, normalizeUrl, sleep, stripProtocol} from "@welshman/lib"
|
||||
import {matchFilters} from "./Filters.js"
|
||||
import type {Repository} from "./Repository.js"
|
||||
import type {Filter} from "./Filters.js"
|
||||
import type {HashedEvent, TrustedEvent} from "./Events.js"
|
||||
|
||||
// Constants and types
|
||||
|
||||
@@ -31,8 +31,8 @@ export type RelayProfile = {
|
||||
// Utils related to bare urls
|
||||
|
||||
export const isRelayUrl = (url: string) => {
|
||||
if (!url.includes('://')) {
|
||||
url = 'wss://' + url
|
||||
if (!url.includes("://")) {
|
||||
url = "wss://" + url
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -47,16 +47,16 @@ export const isRelayUrl = (url: string) => {
|
||||
export const isShareableRelayUrl = (url: string) =>
|
||||
Boolean(
|
||||
isRelayUrl(url) &&
|
||||
// Is it actually a websocket url and has a dot
|
||||
url.match(/^wss:\/\/.+\..+/) &&
|
||||
// Don't match stuff with a port number
|
||||
!url.slice(6).match(/:\d+/) &&
|
||||
// Don't match stuff with a numeric tld
|
||||
!url.slice(6).match(/\.\d+\b/) &&
|
||||
// Don't match raw ip addresses
|
||||
!url.slice(6).match(/\d+\.\d+\.\d+\.\d+/) &&
|
||||
// Skip nostr.wine's virtual relays
|
||||
!url.slice(6).match(/\/npub/)
|
||||
// Is it actually a websocket url and has a dot
|
||||
url.match(/^wss:\/\/.+\..+/) &&
|
||||
// Don't match stuff with a port number
|
||||
!url.slice(6).match(/:\d+/) &&
|
||||
// Don't match stuff with a numeric tld
|
||||
!url.slice(6).match(/\.\d+\b/) &&
|
||||
// Don't match raw ip addresses
|
||||
!url.slice(6).match(/\d+\.\d+\.\d+\.\d+/) &&
|
||||
// Skip nostr.wine's virtual relays
|
||||
!url.slice(6).match(/\/npub/),
|
||||
)
|
||||
|
||||
export const normalizeRelayUrl = (url: string) => {
|
||||
@@ -78,7 +78,8 @@ export const normalizeRelayUrl = (url: string) => {
|
||||
|
||||
export const displayRelayUrl = (url: string) => last(url.split("://")).replace(/\/$/, "")
|
||||
|
||||
export const displayRelayProfile = (profile?: RelayProfile, fallback = "") => profile?.name || fallback
|
||||
export const displayRelayProfile = (profile?: RelayProfile, fallback = "") =>
|
||||
profile?.name || fallback
|
||||
|
||||
// In-memory relay implementation backed by Repository
|
||||
|
||||
@@ -90,10 +91,13 @@ export class Relay<E extends HashedEvent = TrustedEvent> extends Emitter {
|
||||
}
|
||||
|
||||
send(type: string, ...message: any[]) {
|
||||
switch(type) {
|
||||
case 'EVENT': return this.handleEVENT(message as [E])
|
||||
case 'CLOSE': return this.handleCLOSE(message as [string])
|
||||
case 'REQ': return this.handleREQ(message as [string, ...Filter[]])
|
||||
switch (type) {
|
||||
case "EVENT":
|
||||
return this.handleEVENT(message as [E])
|
||||
case "CLOSE":
|
||||
return this.handleCLOSE(message as [string])
|
||||
case "REQ":
|
||||
return this.handleREQ(message as [string, ...Filter[]])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,13 +105,13 @@ export class Relay<E extends HashedEvent = TrustedEvent> extends Emitter {
|
||||
this.repository.publish(event)
|
||||
|
||||
// Callers generally expect async relays
|
||||
sleep(1).then(() => {
|
||||
this.emit('OK', event.id, true, "")
|
||||
void sleep(1).then(() => {
|
||||
this.emit("OK", event.id, true, "")
|
||||
|
||||
if (!this.repository.isDeleted(event)) {
|
||||
for (const [subId, filters] of this.subs.entries()) {
|
||||
if (matchFilters(filters, event)) {
|
||||
this.emit('EVENT', subId, event)
|
||||
this.emit("EVENT", subId, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,12 +126,12 @@ export class Relay<E extends HashedEvent = TrustedEvent> extends Emitter {
|
||||
this.subs.set(subId, filters)
|
||||
|
||||
// Callers generally expect async relays
|
||||
sleep(1).then(() => {
|
||||
void sleep(1).then(() => {
|
||||
for (const event of this.repository.query(filters)) {
|
||||
this.emit('EVENT', subId, event)
|
||||
this.emit("EVENT", subId, event)
|
||||
}
|
||||
|
||||
this.emit('EOSE', subId)
|
||||
this.emit("EOSE", subId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {flatten, Emitter, sortBy, inc, chunk, uniq, omit, now, range, identity} from '@welshman/lib'
|
||||
import {DELETE} from './Kinds'
|
||||
import {EPOCH, matchFilter} from './Filters'
|
||||
import {isReplaceable, isUnwrappedEvent} from './Events'
|
||||
import {getAddress} from './Address'
|
||||
import type {Filter} from './Filters'
|
||||
import type {TrustedEvent, HashedEvent} from './Events'
|
||||
import {flatten, Emitter, sortBy, inc, chunk, uniq, omit, now, range, identity} from "@welshman/lib"
|
||||
import {DELETE} from "./Kinds.js"
|
||||
import {EPOCH, matchFilter} from "./Filters.js"
|
||||
import {isReplaceable, isUnwrappedEvent} from "./Events.js"
|
||||
import {getAddress} from "./Address.js"
|
||||
import type {Filter} from "./Filters.js"
|
||||
import type {TrustedEvent, HashedEvent} from "./Events.js"
|
||||
|
||||
export const DAY = 86400
|
||||
|
||||
@@ -69,22 +69,19 @@ export class Repository<E extends HashedEvent = TrustedEvent> extends Emitter {
|
||||
removed.add(id)
|
||||
}
|
||||
|
||||
this.emit('update', {added, removed})
|
||||
this.emit("update", {added, removed})
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
getEvent = (idOrAddress: string) => {
|
||||
return idOrAddress.includes(':')
|
||||
return idOrAddress.includes(":")
|
||||
? this.eventsByAddress.get(idOrAddress)
|
||||
: this.eventsById.get(idOrAddress)
|
||||
}
|
||||
|
||||
hasEvent = (event: E) => {
|
||||
const duplicate = (
|
||||
this.eventsById.get(event.id) ||
|
||||
this.eventsByAddress.get(getAddress(event))
|
||||
)
|
||||
const duplicate = this.eventsById.get(event.id) || this.eventsByAddress.get(getAddress(event))
|
||||
|
||||
return duplicate && duplicate.created_at >= event.created_at
|
||||
}
|
||||
@@ -110,7 +107,7 @@ export class Repository<E extends HashedEvent = TrustedEvent> extends Emitter {
|
||||
this._updateIndex(this.eventsByDay, getDay(event.created_at), undefined, event)
|
||||
this._updateIndex(this.eventsByAuthor, event.pubkey, undefined, event)
|
||||
|
||||
this.emit('update', {added: [], removed: [event.id]})
|
||||
this.emit("update", {added: [], removed: [event.id]})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,27 +118,28 @@ export class Repository<E extends HashedEvent = TrustedEvent> extends Emitter {
|
||||
|
||||
if (filter.ids) {
|
||||
events = filter.ids!.map(id => this.eventsById.get(id)).filter(identity) as E[]
|
||||
filter = omit(['ids'], filter)
|
||||
filter = omit(["ids"], filter)
|
||||
} else if (filter.authors) {
|
||||
events = uniq(filter.authors!.flatMap(pubkey => this.eventsByAuthor.get(pubkey) || []))
|
||||
filter = omit(['authors'], filter)
|
||||
filter = omit(["authors"], filter)
|
||||
} else if (filter.since || filter.until) {
|
||||
const sinceDay = getDay(filter.since || EPOCH)
|
||||
const untilDay = getDay(filter.until || now())
|
||||
|
||||
events = uniq(
|
||||
Array.from(range(sinceDay, inc(untilDay)))
|
||||
.flatMap((day: number) => this.eventsByDay.get(day) || [])
|
||||
Array.from(range(sinceDay, inc(untilDay))).flatMap(
|
||||
(day: number) => this.eventsByDay.get(day) || [],
|
||||
),
|
||||
)
|
||||
} else {
|
||||
for (const [k, values] of Object.entries(filter)) {
|
||||
if (!k.startsWith('#') || k.length !== 2) {
|
||||
if (!k.startsWith("#") || k.length !== 2) {
|
||||
continue
|
||||
}
|
||||
|
||||
filter = omit([k], filter)
|
||||
events = uniq(
|
||||
(values as string[]).flatMap(v => this.eventsByTag.get(`${k[1]}:${v}`) || [])
|
||||
(values as string[]).flatMap(v => this.eventsByTag.get(`${k[1]}:${v}`) || []),
|
||||
)
|
||||
|
||||
break
|
||||
@@ -218,7 +216,7 @@ export class Repository<E extends HashedEvent = TrustedEvent> extends Emitter {
|
||||
// Update our tag indexes
|
||||
for (const tag of event.tags) {
|
||||
if (tag[0]?.length === 1) {
|
||||
this._updateIndex(this.eventsByTag, tag.slice(0, 2).join(':'), event, duplicate)
|
||||
this._updateIndex(this.eventsByTag, tag.slice(0, 2).join(":"), event, duplicate)
|
||||
|
||||
// If this is a delete event, the tag value is an id or address. Track when it was
|
||||
// deleted so that replaceables can be restored.
|
||||
@@ -235,7 +233,7 @@ export class Repository<E extends HashedEvent = TrustedEvent> extends Emitter {
|
||||
}
|
||||
|
||||
if (shouldNotify) {
|
||||
this.emit('update', {added: [event], removed})
|
||||
this.emit("update", {added: [event], removed})
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {uniq, uniqBy, mapVals, nth, nthEq, ensurePlural} from "@welshman/lib"
|
||||
import {isRelayUrl, isShareableRelayUrl} from "./Relay"
|
||||
import {Address} from "./Address"
|
||||
import {isRelayUrl, isShareableRelayUrl} from "./Relay.js"
|
||||
import {Address} from "./Address.js"
|
||||
|
||||
export const getTags = (types: string | string[], tags: string[][]) => {
|
||||
types = ensurePlural(types)
|
||||
@@ -17,8 +17,7 @@ export const getTag = (types: string | string[], tags: string[][]) => {
|
||||
export const getTagValues = (types: string | string[], tags: string[][]) =>
|
||||
getTags(types, tags).map(nth(1))
|
||||
|
||||
export const getTagValue = (types: string | string[], tags: string[][]) =>
|
||||
getTag(types, tags)?.[1]
|
||||
export const getTagValue = (types: string | string[], tags: string[][]) => getTag(types, tags)?.[1]
|
||||
|
||||
export const getEventTags = (tags: string[][]) =>
|
||||
tags.filter(t => ["e"].includes(t[0]) && t[1].length === 64)
|
||||
@@ -37,7 +36,8 @@ export const getPubkeyTagValues = (tags: string[][]) => getPubkeyTags(tags).map(
|
||||
|
||||
export const getTopicTags = (tags: string[][]) => tags.filter(nthEq(0, "t"))
|
||||
|
||||
export const getTopicTagValues = (tags: string[][]) => getTopicTags(tags).map(t => t[1].replace(/^#/, ''))
|
||||
export const getTopicTagValues = (tags: string[][]) =>
|
||||
getTopicTags(tags).map(t => t[1].replace(/^#/, ""))
|
||||
|
||||
export const getRelayTags = (tags: string[][]) =>
|
||||
tags.filter(t => ["r", "relay"].includes(t[0]) && isRelayUrl(t[1] || ""))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {hexToBech32, fromPairs} from '@welshman/lib'
|
||||
import type {TrustedEvent} from './Events'
|
||||
import {hexToBech32, fromPairs} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "./Events.js"
|
||||
|
||||
const DIVISORS = {
|
||||
m: BigInt(1e3),
|
||||
@@ -71,7 +71,7 @@ export const getLnUrl = (address: string) => {
|
||||
|
||||
export type Zapper = {
|
||||
lnurl: string
|
||||
pubkey?: string,
|
||||
pubkey?: string
|
||||
callback?: string
|
||||
minSendable?: number
|
||||
maxSendable?: number
|
||||
@@ -81,7 +81,7 @@ export type Zapper = {
|
||||
|
||||
export type Zap = {
|
||||
request: TrustedEvent
|
||||
response: TrustedEvent,
|
||||
response: TrustedEvent
|
||||
invoiceAmount: number
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -1,13 +1,13 @@
|
||||
export * from './Address'
|
||||
export * from './Encryptable'
|
||||
export * from './Events'
|
||||
export * from './Filters'
|
||||
export * from './Handler'
|
||||
export * from './Kinds'
|
||||
export * from './Links'
|
||||
export * from './List'
|
||||
export * from './Profile'
|
||||
export * from './Relay'
|
||||
export * from './Repository'
|
||||
export * from './Tags'
|
||||
export * from './Zaps'
|
||||
export * from "./Address.js"
|
||||
export * from "./Encryptable.js"
|
||||
export * from "./Events.js"
|
||||
export * from "./Filters.js"
|
||||
export * from "./Handler.js"
|
||||
export * from "./Kinds.js"
|
||||
export * from "./Links.js"
|
||||
export * from "./List.js"
|
||||
export * from "./Profile.js"
|
||||
export * from "./Relay.js"
|
||||
export * from "./Repository.js"
|
||||
export * from "./Tags.js"
|
||||
export * from "./Zaps.js"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"targets": [
|
||||
{"extname": ".cjs", "module": "commonjs"},
|
||||
{"extname": ".mjs", "module": "esnext", "moduleResolution": "node"}
|
||||
],
|
||||
"projects": ["tsconfig.json"]
|
||||
}
|
||||
@@ -3,9 +3,13 @@
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "build",
|
||||
"esModuleInterop": true,
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"skipLibCheck": true,
|
||||
"lib": ["esnext", "dom"]
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user