unsplit, backwards-compatibility, wasm relay and pool must be configured manually from the abstract classes.
This commit is contained in:
parent
a4ae964ee6
commit
7f11c0c618
|
@ -5,3 +5,4 @@ package-lock.json
|
|||
.envrc
|
||||
lib
|
||||
test.html
|
||||
bench.js
|
||||
|
|
17
README.md
17
README.md
|
@ -210,8 +210,8 @@ Importing the entirety of `nostr-tools` may bloat your build, so you should prob
|
|||
|
||||
```js
|
||||
import { generateSecretKey, finalizeEvent, verifyEvent } from 'nostr-tools/pure'
|
||||
import SimplePool from 'nostr-tools/pool-pure'
|
||||
import Relay, { Subscription } from 'nostr-tools/relay-pure'
|
||||
import { SimplePool } from 'nostr-tools/pool-pure'
|
||||
import { Relay, Subscription } from 'nostr-tools/relay'
|
||||
import { matchFilter } from 'nostr-tools/filter'
|
||||
import { decode, nprofileEncode, neventEncode, npubEncode } from 'nostr-tools/nip19'
|
||||
// and so on and so forth
|
||||
|
@ -232,11 +232,18 @@ initNostrWasm().then(setNostrWasm)
|
|||
// see https://www.npmjs.com/package/nostr-wasm for options
|
||||
```
|
||||
|
||||
If you're going to use `Relay` and `SimplePool` you must also import `nostr-tools/relay-wasm` and/or `nostr-tools/pool-wasm` instead of the defaults:
|
||||
If you're going to use `Relay` and `SimplePool` you must also import `nostr-tools/abstract-relay` and/or `nostr-tools/abstract-pool` instead of the defaults and then instantiate them by passing the `verifyEvent`:
|
||||
|
||||
```js
|
||||
import Relay, { Subscription } from 'nostr-tools/relay-wasm'
|
||||
import SimplePool from 'nostr-tools/pool-wasm'
|
||||
import { setNostrWasm, verifyEvent } from 'nostr-tools/wasm'
|
||||
import { AbstractRelay } from 'nostr-tools/abstract-relay'
|
||||
import { AbstractSimplePool } from 'nostr-tools/abstract-pool'
|
||||
import { initNostrWasm } from 'nostr-wasm'
|
||||
|
||||
initNostrWasm().then(setNostrWasm)
|
||||
|
||||
const relay = AbstractRelay.connect('wss://relayable.org', { verifyEvent })
|
||||
const pool = new AbstractSimplePool({ verifyEvent })
|
||||
```
|
||||
|
||||
This may be faster than the pure-JS [noble libraries](https://paulmillr.com/noble/) used by default and in `nostr-tools/pure`.
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import Relay, { SubscriptionParams, Subscription } from './trusted-relay.ts'
|
||||
import { AbstractRelay as AbstractRelay, SubscriptionParams, Subscription } from './abstract-relay.ts'
|
||||
import { normalizeURL } from './utils.ts'
|
||||
|
||||
import type { Event, Nostr } from './core.ts'
|
||||
import { type Filter } from './filter.ts'
|
||||
import { alwaysTrue } from './helpers.ts'
|
||||
|
||||
export type SubCloser = { close: () => void }
|
||||
|
||||
|
@ -12,25 +13,25 @@ export type SubscribeManyParams = Omit<SubscriptionParams, 'onclose' | 'id'> & {
|
|||
id?: string
|
||||
}
|
||||
|
||||
export default class TrustedSimplePool {
|
||||
private relays = new Map<string, Relay>()
|
||||
public seenOn = new Map<string, Set<Relay>>()
|
||||
export class AbstractSimplePool {
|
||||
private relays = new Map<string, AbstractRelay>()
|
||||
public seenOn = new Map<string, Set<AbstractRelay>>()
|
||||
public trackRelays: boolean = false
|
||||
|
||||
public verifyEvent: Nostr['verifyEvent'] | undefined
|
||||
public verifyEvent: Nostr['verifyEvent']
|
||||
public trustedRelayURLs = new Set<string>()
|
||||
|
||||
constructor(opts: { verifyEvent?: Nostr['verifyEvent'] } = {}) {
|
||||
constructor(opts: { verifyEvent: Nostr['verifyEvent'] }) {
|
||||
this.verifyEvent = opts.verifyEvent
|
||||
}
|
||||
|
||||
async ensureRelay(url: string, params?: { connectionTimeout?: number }): Promise<Relay> {
|
||||
async ensureRelay(url: string, params?: { connectionTimeout?: number }): Promise<AbstractRelay> {
|
||||
url = normalizeURL(url)
|
||||
|
||||
let relay = this.relays.get(url)
|
||||
if (!relay) {
|
||||
relay = new Relay(url, {
|
||||
verifyEvent: this.trustedRelayURLs.has(url) ? undefined : this.verifyEvent,
|
||||
relay = new AbstractRelay(url, {
|
||||
verifyEvent: this.trustedRelayURLs.has(url) ? alwaysTrue : this.verifyEvent,
|
||||
})
|
||||
if (params?.connectionTimeout) relay.connectionTimeout = params.connectionTimeout
|
||||
this.relays.set(url, relay)
|
||||
|
@ -48,7 +49,7 @@ export default class TrustedSimplePool {
|
|||
|
||||
subscribeMany(relays: string[], filters: Filter[], params: SubscribeManyParams): SubCloser {
|
||||
if (this.trackRelays) {
|
||||
params.receivedEvent = (relay: Relay, id: string) => {
|
||||
params.receivedEvent = (relay: AbstractRelay, id: string) => {
|
||||
let set = this.seenOn.get(id)
|
||||
if (!set) {
|
||||
set = new Set()
|
||||
|
@ -99,7 +100,7 @@ export default class TrustedSimplePool {
|
|||
return
|
||||
}
|
||||
|
||||
let relay: Relay
|
||||
let relay: AbstractRelay
|
||||
try {
|
||||
relay = await this.ensureRelay(url, {
|
||||
connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1000) : undefined,
|
|
@ -4,10 +4,10 @@ import type { Event, EventTemplate, Nostr } from './core.ts'
|
|||
import { matchFilters, type Filter } from './filter.ts'
|
||||
import { getHex64, getSubscriptionId } from './fakejson.ts'
|
||||
import { Queue, normalizeURL } from './utils.ts'
|
||||
import { nip42 } from './index.ts'
|
||||
import { makeAuthEvent } from './nip42.ts'
|
||||
import { yieldThread } from './helpers.ts'
|
||||
|
||||
export default class TrustedRelay {
|
||||
export class AbstractRelay {
|
||||
public readonly url: string
|
||||
private _connected: boolean = false
|
||||
|
||||
|
@ -16,10 +16,10 @@ export default class TrustedRelay {
|
|||
|
||||
public baseEoseTimeout: number = 4400
|
||||
public connectionTimeout: number = 4400
|
||||
public openSubs = new Map<string, Subscription>()
|
||||
private connectionTimeoutHandle: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
private connectionPromise: Promise<void> | undefined
|
||||
private openSubs = new Map<string, Subscription>()
|
||||
private openCountRequests = new Map<string, CountResolver>()
|
||||
private openEventPublishes = new Map<string, EventPublishResolver>()
|
||||
private ws: WebSocket | undefined
|
||||
|
@ -27,15 +27,15 @@ export default class TrustedRelay {
|
|||
private queueRunning = false
|
||||
private challenge: string | undefined
|
||||
private serial: number = 0
|
||||
private verifyEvent: Nostr['verifyEvent'] | undefined
|
||||
private verifyEvent: Nostr['verifyEvent']
|
||||
|
||||
constructor(url: string, opts: { verifyEvent?: Nostr['verifyEvent'] } = {}) {
|
||||
constructor(url: string, opts: { verifyEvent: Nostr['verifyEvent'] }) {
|
||||
this.url = normalizeURL(url)
|
||||
this.verifyEvent = opts.verifyEvent
|
||||
}
|
||||
|
||||
static async connect(url: string, opts: { verifyEvent?: Nostr['verifyEvent'] } = {}) {
|
||||
const relay = new TrustedRelay(url, opts)
|
||||
static async connect(url: string, opts: { verifyEvent: Nostr['verifyEvent'] }) {
|
||||
const relay = new AbstractRelay(url, opts)
|
||||
await relay.connect()
|
||||
return relay
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ export default class TrustedRelay {
|
|||
case 'EVENT': {
|
||||
const so = this.openSubs.get(data[1] as string) as Subscription
|
||||
const event = data[2] as Event
|
||||
if ((this.verifyEvent ? this.verifyEvent(event) : true) && matchFilters(so.filters, event)) {
|
||||
if (this.verifyEvent(event) && matchFilters(so.filters, event)) {
|
||||
so.onevent(event)
|
||||
}
|
||||
return
|
||||
|
@ -200,7 +200,6 @@ export default class TrustedRelay {
|
|||
if (!so) return
|
||||
so.closed = true
|
||||
so.close(data[2] as string)
|
||||
this.openSubs.delete(id)
|
||||
return
|
||||
}
|
||||
case 'NOTICE':
|
||||
|
@ -226,7 +225,7 @@ export default class TrustedRelay {
|
|||
|
||||
public async auth(signAuthEvent: (authEvent: EventTemplate) => Promise<void>) {
|
||||
if (!this.challenge) throw new Error("can't perform auth, no challenge was received")
|
||||
const evt = nip42.makeAuthEvent(this.url, this.challenge)
|
||||
const evt = makeAuthEvent(this.url, this.challenge)
|
||||
await signAuthEvent(evt)
|
||||
this.send('["AUTH",' + JSON.stringify(evt) + ']')
|
||||
}
|
||||
|
@ -268,17 +267,25 @@ export default class TrustedRelay {
|
|||
this._connected = false
|
||||
this.ws?.close()
|
||||
}
|
||||
|
||||
// this method simulates receiving a message from the websocket
|
||||
public _push(msg: string) {
|
||||
this.incomingMessageQueue.enqueue(msg)
|
||||
if (!this.queueRunning) {
|
||||
this.runQueue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Subscription {
|
||||
public readonly relay: TrustedRelay
|
||||
public readonly relay: AbstractRelay
|
||||
public readonly id: string
|
||||
|
||||
public closed: boolean = false
|
||||
public eosed: boolean = false
|
||||
public filters: Filter[]
|
||||
public alreadyHaveEvent: ((id: string) => boolean) | undefined
|
||||
public receivedEvent: ((relay: TrustedRelay, id: string) => void) | undefined
|
||||
public receivedEvent: ((relay: AbstractRelay, id: string) => void) | undefined
|
||||
|
||||
public onevent: (evt: Event) => void
|
||||
public oneose: (() => void) | undefined
|
||||
|
@ -287,7 +294,7 @@ export class Subscription {
|
|||
public eoseTimeout: number
|
||||
private eoseTimeoutHandle: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
constructor(relay: TrustedRelay, id: string, filters: Filter[], params: SubscriptionParams) {
|
||||
constructor(relay: AbstractRelay, id: string, filters: Filter[], params: SubscriptionParams) {
|
||||
this.relay = relay
|
||||
this.filters = filters
|
||||
this.id = id
|
||||
|
@ -328,6 +335,7 @@ export class Subscription {
|
|||
this.relay.send('["CLOSE",' + JSON.stringify(this.id) + ']')
|
||||
this.closed = true
|
||||
}
|
||||
this.relay.openSubs.delete(this.id)
|
||||
this.onclose?.(reason)
|
||||
}
|
||||
}
|
||||
|
@ -337,7 +345,7 @@ export type SubscriptionParams = {
|
|||
oneose?: () => void
|
||||
onclose?: (reason: string) => void
|
||||
alreadyHaveEvent?: (id: string) => boolean
|
||||
receivedEvent?: (relay: TrustedRelay, id: string) => void
|
||||
receivedEvent?: (relay: AbstractRelay, id: string) => void
|
||||
eoseTimeout?: number
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import { initNostrWasm } from 'nostr-wasm'
|
||||
import { NostrEvent } from './core'
|
||||
import { finalizeEvent, generateSecretKey } from './pure'
|
||||
import { setNostrWasm, verifyEvent } from './wasm'
|
||||
import { AbstractRelay } from './abstract-relay.ts'
|
||||
import { Relay as PureRelay } from './relay.ts'
|
||||
import { alwaysTrue } from './helpers.ts'
|
||||
|
||||
const RUNS = 400
|
||||
|
||||
let messages: string[] = []
|
||||
let baseContent = ''
|
||||
for (let i = 0; i < RUNS; i++) {
|
||||
baseContent += 'a'
|
||||
}
|
||||
const secretKey = generateSecretKey()
|
||||
for (let i = 0; i < RUNS / 100; i++) {
|
||||
const tags = []
|
||||
for (let t = 0; t < i; t++) {
|
||||
tags.push(['t', 'nada'])
|
||||
}
|
||||
const event = { created_at: Math.round(Date.now()) / 1000, kind: 1, content: baseContent.slice(0, RUNS - i), tags }
|
||||
const signed = finalizeEvent(event, secretKey)
|
||||
messages.push(JSON.stringify(['EVENT', '_', signed]))
|
||||
}
|
||||
|
||||
setNostrWasm(await initNostrWasm())
|
||||
|
||||
const pureRelay = new PureRelay('wss://pure.com/')
|
||||
const trustedRelay = new AbstractRelay('wss://trusted.com/', { verifyEvent: alwaysTrue })
|
||||
const wasmRelay = new AbstractRelay('wss://wasm.com/', { verifyEvent })
|
||||
|
||||
const run = (relay: AbstractRelay) => async () => {
|
||||
return new Promise<void>(resolve => {
|
||||
let received = 0
|
||||
let sub = relay.prepareSubscription([{}], {
|
||||
onevent(_: NostrEvent) {
|
||||
received++
|
||||
if (received === messages.length - 1) {
|
||||
resolve()
|
||||
sub.closed = true
|
||||
sub.close()
|
||||
}
|
||||
},
|
||||
id: '_',
|
||||
})
|
||||
for (let e = 0; e < messages.length; e++) {
|
||||
relay._push(messages[e])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const benchmarks: Record<string, { test: () => Promise<void>; runs: number[] }> = {
|
||||
trusted: { test: run(trustedRelay), runs: [] },
|
||||
pure: { test: run(pureRelay), runs: [] },
|
||||
wasm: { test: run(wasmRelay), runs: [] },
|
||||
}
|
||||
|
||||
for (let b = 0; b < 50; b++) {
|
||||
for (let name in benchmarks) {
|
||||
const { test, runs } = benchmarks[name]
|
||||
const before = performance.now()
|
||||
await test()
|
||||
runs.push(performance.now() - before)
|
||||
}
|
||||
}
|
||||
|
||||
for (let name in benchmarks) {
|
||||
const { runs } = benchmarks[name]
|
||||
console.log(name, runs.reduce((a, b) => a + b, 0) / runs.length)
|
||||
}
|
1
build.js
1
build.js
|
@ -10,6 +10,7 @@ const entryPoints = fs
|
|||
file !== 'core.ts' &&
|
||||
file !== 'test-helpers.ts' &&
|
||||
file !== 'helpers.ts' &&
|
||||
file !== 'benchmarks.ts' &&
|
||||
!file.endsWith('.test.ts') &&
|
||||
fs.statSync(join(process.cwd(), file)).isFile(),
|
||||
)
|
||||
|
|
1
core.ts
1
core.ts
|
@ -19,6 +19,7 @@ export interface Event {
|
|||
[verifiedSymbol]?: boolean
|
||||
}
|
||||
|
||||
export type NostrEvent = Event
|
||||
export type EventTemplate = Pick<Event, 'kind' | 'tags' | 'content' | 'created_at'>
|
||||
export type UnsignedEvent = Pick<Event, 'kind' | 'tags' | 'content' | 'created_at' | 'pubkey'>
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { verifiedSymbol, type Event, type Nostr } from './core.ts'
|
||||
|
||||
export async function yieldThread() {
|
||||
return new Promise(resolve => {
|
||||
const ch = new MessageChannel()
|
||||
|
@ -7,3 +9,8 @@ export async function yieldThread() {
|
|||
ch.port1.start()
|
||||
})
|
||||
}
|
||||
|
||||
export const alwaysTrue: Nostr['verifyEvent'] = (t: Event) => {
|
||||
t[verifiedSymbol] = true
|
||||
return t[verifiedSymbol]
|
||||
}
|
||||
|
|
44
package.json
44
package.json
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"type": "module",
|
||||
"name": "nostr-tools",
|
||||
"version": "2.1.0",
|
||||
"description": "Tools for making a Nostr client.",
|
||||
|
@ -39,35 +40,25 @@
|
|||
"require": "./lib/cjs/filter.js",
|
||||
"types": "./lib/types/filter.d.ts"
|
||||
},
|
||||
"./trusted-relay": {
|
||||
"import": "./lib/esm/trusted-relay.js",
|
||||
"require": "./lib/cjs/trusted-relay.js",
|
||||
"types": "./lib/types/trusted-relay.d.ts"
|
||||
"./abstract-relay": {
|
||||
"import": "./lib/esm/abstract-relay.js",
|
||||
"require": "./lib/cjs/abstract-relay.js",
|
||||
"types": "./lib/types/abstract-relay.d.ts"
|
||||
},
|
||||
"./relay-wasm": {
|
||||
"import": "./lib/esm/relay-wasm.js",
|
||||
"require": "./lib/cjs/relay-wasm.js",
|
||||
"types": "./lib/types/relay-wasm.d.ts"
|
||||
"./relay": {
|
||||
"import": "./lib/esm/relay.js",
|
||||
"require": "./lib/cjs/relay.js",
|
||||
"types": "./lib/types/relay.d.ts"
|
||||
},
|
||||
"./relay-pure": {
|
||||
"import": "./lib/esm/relay-pure.js",
|
||||
"require": "./lib/cjs/relay-pure.js",
|
||||
"types": "./lib/types/relay-pure.d.ts"
|
||||
"./abstract-pool": {
|
||||
"import": "./lib/esm/abstract-pool.js",
|
||||
"require": "./lib/cjs/abstract-pool.js",
|
||||
"types": "./lib/types/abstract-pool.d.ts"
|
||||
},
|
||||
"./trusted-pool": {
|
||||
"import": "./lib/esm/trusted-pool.js",
|
||||
"require": "./lib/cjs/trusted-pool.js",
|
||||
"types": "./lib/types/trusted-pool.d.ts"
|
||||
},
|
||||
"./pool-wasm": {
|
||||
"import": "./lib/esm/pool-wasm.js",
|
||||
"require": "./lib/cjs/pool-wasm.js",
|
||||
"types": "./lib/types/pool-wasm.d.ts"
|
||||
},
|
||||
"./pool-pure": {
|
||||
"import": "./lib/esm/pool-pure.js",
|
||||
"require": "./lib/cjs/pool-pure.js",
|
||||
"types": "./lib/types/pool-pure.d.ts"
|
||||
"./pool": {
|
||||
"import": "./lib/esm/pool.js",
|
||||
"require": "./lib/cjs/pool.js",
|
||||
"types": "./lib/types/pool.d.ts"
|
||||
},
|
||||
"./references": {
|
||||
"import": "./lib/esm/references.js",
|
||||
|
@ -183,6 +174,7 @@
|
|||
"@scure/base": "1.1.1",
|
||||
"@scure/bip32": "1.3.1",
|
||||
"@scure/bip39": "1.2.1",
|
||||
"mitata": "^0.1.6",
|
||||
"nostr-wasm": "v0.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
10
pool-pure.ts
10
pool-pure.ts
|
@ -1,10 +0,0 @@
|
|||
import { verifyEvent } from './pure.ts'
|
||||
import TrustedSimplePool from './trusted-pool.ts'
|
||||
|
||||
export default class PureSimplePool extends TrustedSimplePool {
|
||||
constructor() {
|
||||
super({ verifyEvent })
|
||||
}
|
||||
}
|
||||
|
||||
export * from './trusted-pool.ts'
|
|
@ -1,7 +1,7 @@
|
|||
import { verifyEvent } from './wasm.ts'
|
||||
import TrustedSimplePool from './trusted-pool.ts'
|
||||
import { TrustedSimplePool } from './trusted-pool.ts'
|
||||
|
||||
export default class WasmSimplePool extends TrustedSimplePool {
|
||||
export class SimplePool extends TrustedSimplePool {
|
||||
constructor() {
|
||||
super({ verifyEvent })
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { test, expect, afterAll } from 'bun:test'
|
|||
|
||||
import { finalizeEvent, type Event } from './pure.ts'
|
||||
import { generateSecretKey, getPublicKey } from './pure.ts'
|
||||
import { SimplePool } from './pool.ts'
|
||||
import SimplePool from './pool-pure.ts'
|
||||
|
||||
let pool = new SimplePool()
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import { verifyEvent } from './pure.ts'
|
||||
import { AbstractSimplePool } from './abstract-pool.ts'
|
||||
|
||||
export class SimplePool extends AbstractSimplePool {
|
||||
constructor() {
|
||||
super({ verifyEvent })
|
||||
}
|
||||
}
|
||||
|
||||
export * from './abstract-pool.ts'
|
|
@ -1,16 +0,0 @@
|
|||
import { verifyEvent } from './wasm.ts'
|
||||
import TrustedRelay from './trusted-relay.ts'
|
||||
|
||||
export default class WasmRelay extends TrustedRelay {
|
||||
constructor(url: string) {
|
||||
super(url, { verifyEvent })
|
||||
}
|
||||
|
||||
static async connect(url: string) {
|
||||
const relay = new WasmRelay(url)
|
||||
await relay.connect()
|
||||
return relay
|
||||
}
|
||||
}
|
||||
|
||||
export * from './trusted-relay.ts'
|
|
@ -1,16 +1,16 @@
|
|||
import { verifyEvent } from './pure.ts'
|
||||
import TrustedRelay from './trusted-relay.ts'
|
||||
import { AbstractRelay } from './abstract-relay.ts'
|
||||
|
||||
export default class PureRelay extends TrustedRelay {
|
||||
export class Relay extends AbstractRelay {
|
||||
constructor(url: string) {
|
||||
super(url, { verifyEvent })
|
||||
}
|
||||
|
||||
static async connect(url: string) {
|
||||
const relay = new PureRelay(url)
|
||||
const relay = new Relay(url)
|
||||
await relay.connect()
|
||||
return relay
|
||||
}
|
||||
}
|
||||
|
||||
export * from './trusted-relay.ts'
|
||||
export * from './abstract-relay.ts'
|
Loading…
Reference in New Issue