remove the kind type parameter from events and filters.

This commit is contained in:
fiatjaf 2023-12-16 10:10:37 -03:00
parent 1939c46eaa
commit 6a07e7c1cc
No known key found for this signature in database
GPG Key ID: BAD43C4BE5C1A3A1
21 changed files with 157 additions and 211 deletions

View File

@ -1,44 +1,23 @@
import { import {
getBlankEvent,
finishEvent, finishEvent,
serializeEvent, serializeEvent,
getEventHash, getEventHash,
validateEvent, validateEvent,
verifySignature, verifySignature,
getSignature, getSignature,
Kind,
verifiedSymbol, verifiedSymbol,
} from './event.ts' } from './event.ts'
import { getPublicKey } from './keys.ts' import { getPublicKey } from './keys.ts'
import { ShortTextNote } from './kinds.ts'
describe('Event', () => { describe('Event', () => {
describe('getBlankEvent', () => {
it('should return a blank event object', () => {
expect(getBlankEvent()).toEqual({
kind: 255,
content: '',
tags: [],
created_at: 0,
})
})
it('should return a blank event object with defined kind', () => {
expect(getBlankEvent(Kind.Text)).toEqual({
kind: 1,
content: '',
tags: [],
created_at: 0,
})
})
})
describe('finishEvent', () => { describe('finishEvent', () => {
it('should create a signed event from a template', () => { it('should create a signed event from a template', () => {
const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf'
const publicKey = getPublicKey(privateKey) const publicKey = getPublicKey(privateKey)
const template = { const template = {
kind: Kind.Text, kind: ShortTextNote,
tags: [], tags: [],
content: 'Hello, world!', content: 'Hello, world!',
created_at: 1617932115, created_at: 1617932115,
@ -64,7 +43,7 @@ describe('Event', () => {
const unsignedEvent = { const unsignedEvent = {
pubkey: publicKey, pubkey: publicKey,
created_at: 1617932115, created_at: 1617932115,
kind: Kind.Text, kind: ShortTextNote,
tags: [], tags: [],
content: 'Hello, world!', content: 'Hello, world!',
} }
@ -88,7 +67,7 @@ describe('Event', () => {
const publicKey = getPublicKey(privateKey) const publicKey = getPublicKey(privateKey)
const invalidEvent = { const invalidEvent = {
kind: Kind.Text, kind: ShortTextNote,
tags: [], tags: [],
created_at: 1617932115, created_at: 1617932115,
pubkey: publicKey, // missing content pubkey: publicKey, // missing content
@ -107,7 +86,7 @@ describe('Event', () => {
const publicKey = getPublicKey(privateKey) const publicKey = getPublicKey(privateKey)
const unsignedEvent = { const unsignedEvent = {
kind: Kind.Text, kind: ShortTextNote,
tags: [], tags: [],
content: 'Hello, world!', content: 'Hello, world!',
created_at: 1617932115, created_at: 1617932115,
@ -127,7 +106,7 @@ describe('Event', () => {
const publicKey = getPublicKey(privateKey) const publicKey = getPublicKey(privateKey)
const unsignedEvent = { const unsignedEvent = {
kind: Kind.Text, kind: ShortTextNote,
tags: [], tags: [],
content: 'Hello, world!', content: 'Hello, world!',
created_at: 1617932115, created_at: 1617932115,
@ -149,7 +128,7 @@ describe('Event', () => {
it('should return false for an event object with missing properties', () => { it('should return false for an event object with missing properties', () => {
const invalidEvent = { const invalidEvent = {
kind: Kind.Text, kind: ShortTextNote,
tags: [], tags: [],
created_at: 1617932115, // missing content and pubkey created_at: 1617932115, // missing content and pubkey
} }
@ -221,7 +200,7 @@ describe('Event', () => {
const event = finishEvent( const event = finishEvent(
{ {
kind: Kind.Text, kind: ShortTextNote,
tags: [], tags: [],
content: 'Hello, world!', content: 'Hello, world!',
created_at: 1617932115, created_at: 1617932115,
@ -239,7 +218,7 @@ describe('Event', () => {
const { [verifiedSymbol]: _, ...event } = finishEvent( const { [verifiedSymbol]: _, ...event } = finishEvent(
{ {
kind: Kind.Text, kind: ShortTextNote,
tags: [], tags: [],
content: 'Hello, world!', content: 'Hello, world!',
created_at: 1617932115, created_at: 1617932115,
@ -263,7 +242,7 @@ describe('Event', () => {
const { [verifiedSymbol]: _, ...event } = finishEvent( const { [verifiedSymbol]: _, ...event } = finishEvent(
{ {
kind: Kind.Text, kind: ShortTextNote,
tags: [], tags: [],
content: 'Hello, world!', content: 'Hello, world!',
created_at: 1617932115, created_at: 1617932115,
@ -308,7 +287,7 @@ describe('Event', () => {
const publicKey = getPublicKey(privateKey) const publicKey = getPublicKey(privateKey)
const unsignedEvent = { const unsignedEvent = {
kind: Kind.Text, kind: ShortTextNote,
tags: [], tags: [],
content: 'Hello, world!', content: 'Hello, world!',
created_at: 1617932115, created_at: 1617932115,
@ -336,7 +315,7 @@ describe('Event', () => {
const wrongPrivateKey = 'a91e2a9d9e0f70f0877bea0dbf034e8f95d7392a27a7f07da0d14b9e9d456be7' const wrongPrivateKey = 'a91e2a9d9e0f70f0877bea0dbf034e8f95d7392a27a7f07da0d14b9e9d456be7'
const unsignedEvent = { const unsignedEvent = {
kind: Kind.Text, kind: ShortTextNote,
tags: [], tags: [],
content: 'Hello, world!', content: 'Hello, world!',
created_at: 1617932115, created_at: 1617932115,

View File

@ -8,8 +8,8 @@ import { utf8Encoder } from './utils.ts'
/** Designates a verified event signature. */ /** Designates a verified event signature. */
export const verifiedSymbol = Symbol('verified') export const verifiedSymbol = Symbol('verified')
export interface Event<K extends number = number> { export interface Event {
kind: K kind: number
tags: string[][] tags: string[][]
content: string content: string
created_at: number created_at: number
@ -19,30 +19,16 @@ export interface Event<K extends number = number> {
[verifiedSymbol]?: boolean [verifiedSymbol]?: boolean
} }
export type EventTemplate<K extends number = number> = Pick<Event<K>, 'kind' | 'tags' | 'content' | 'created_at'> export type EventTemplate = Pick<Event, 'kind' | 'tags' | 'content' | 'created_at'>
export type UnsignedEvent<K extends number = number> = Pick< export type UnsignedEvent = Pick<Event, 'kind' | 'tags' | 'content' | 'created_at' | 'pubkey'>
Event<K>,
'kind' | 'tags' | 'content' | 'created_at' | 'pubkey'
>
/** An event whose signature has been verified. */ /** An event whose signature has been verified. */
export interface VerifiedEvent<K extends number = number> extends Event<K> { export interface VerifiedEvent extends Event {
[verifiedSymbol]: true [verifiedSymbol]: true
} }
export function getBlankEvent(): EventTemplate<Kind.Blank> export function finishEvent(t: EventTemplate, privateKey: string): VerifiedEvent {
export function getBlankEvent<K extends number>(kind: K): EventTemplate<K> const event = t as VerifiedEvent
export function getBlankEvent<K>(kind: K | Kind.Blank = Kind.Blank) {
return {
kind,
content: '',
tags: [],
created_at: 0,
}
}
export function finishEvent<K extends number = number>(t: EventTemplate<K>, privateKey: string): VerifiedEvent<K> {
const event = t as VerifiedEvent<K>
event.pubkey = getPublicKey(privateKey) event.pubkey = getPublicKey(privateKey)
event.id = getEventHash(event) event.id = getEventHash(event)
event.sig = getSignature(event, privateKey) event.sig = getSignature(event, privateKey)
@ -50,20 +36,20 @@ export function finishEvent<K extends number = number>(t: EventTemplate<K>, priv
return event return event
} }
export function serializeEvent(evt: UnsignedEvent<number>): string { export function serializeEvent(evt: UnsignedEvent): string {
if (!validateEvent(evt)) throw new Error("can't serialize event with wrong or missing properties") if (!validateEvent(evt)) throw new Error("can't serialize event with wrong or missing properties")
return JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content]) return JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content])
} }
export function getEventHash(event: UnsignedEvent<number>): string { export function getEventHash(event: UnsignedEvent): string {
let eventHash = sha256(utf8Encoder.encode(serializeEvent(event))) let eventHash = sha256(utf8Encoder.encode(serializeEvent(event)))
return bytesToHex(eventHash) return bytesToHex(eventHash)
} }
const isRecord = (obj: unknown): obj is Record<string, unknown> => obj instanceof Object const isRecord = (obj: unknown): obj is Record<string, unknown> => obj instanceof Object
export function validateEvent<T>(event: T): event is T & UnsignedEvent<number> { export function validateEvent<T>(event: T): event is T & UnsignedEvent {
if (!isRecord(event)) return false if (!isRecord(event)) return false
if (typeof event.kind !== 'number') return false if (typeof event.kind !== 'number') return false
if (typeof event.content !== 'string') return false if (typeof event.content !== 'string') return false
@ -84,7 +70,7 @@ export function validateEvent<T>(event: T): event is T & UnsignedEvent<number> {
} }
/** Verify the event's signature. This function mutates the event with a `verified` symbol, making it idempotent. */ /** Verify the event's signature. This function mutates the event with a `verified` symbol, making it idempotent. */
export function verifySignature<K extends number>(event: Event<K>): event is VerifiedEvent<K> { export function verifySignature(event: Event): event is VerifiedEvent {
if (typeof event[verifiedSymbol] === 'boolean') return event[verifiedSymbol] if (typeof event[verifiedSymbol] === 'boolean') return event[verifiedSymbol]
const hash = getEventHash(event) const hash = getEventHash(event)
@ -100,7 +86,7 @@ export function verifySignature<K extends number>(event: Event<K>): event is Ver
} }
/** @deprecated Use `getSignature` instead. */ /** @deprecated Use `getSignature` instead. */
export function signEvent(event: UnsignedEvent<number>, key: string): string { export function signEvent(event: UnsignedEvent, key: string): string {
console.warn( console.warn(
'nostr-tools: `signEvent` is deprecated and will be removed or changed in the future. Please use `getSignature` instead.', 'nostr-tools: `signEvent` is deprecated and will be removed or changed in the future. Please use `getSignature` instead.',
) )
@ -108,6 +94,6 @@ export function signEvent(event: UnsignedEvent<number>, key: string): string {
} }
/** Calculate the signature for an event. */ /** Calculate the signature for an event. */
export function getSignature(event: UnsignedEvent<number>, key: string): string { export function getSignature(event: UnsignedEvent, key: string): string {
return bytesToHex(schnorr.sign(getEventHash(event), key)) return bytesToHex(schnorr.sign(getEventHash(event), key))
} }

View File

@ -1,8 +1,8 @@
import { Event } from './event.ts' import { Event } from './event.ts'
export type Filter<K extends number = number> = { export type Filter = {
ids?: string[] ids?: string[]
kinds?: K[] kinds?: number[]
authors?: string[] authors?: string[]
since?: number since?: number
until?: number until?: number
@ -11,7 +11,7 @@ export type Filter<K extends number = number> = {
[key: `#${string}`]: string[] | undefined [key: `#${string}`]: string[] | undefined
} }
export function matchFilter(filter: Filter<number>, event: Event<number>): boolean { export function matchFilter(filter: Filter, event: Event): boolean {
if (filter.ids && filter.ids.indexOf(event.id) === -1) { if (filter.ids && filter.ids.indexOf(event.id) === -1) {
if (!filter.ids.some(prefix => event.id.startsWith(prefix))) { if (!filter.ids.some(prefix => event.id.startsWith(prefix))) {
return false return false
@ -38,15 +38,15 @@ export function matchFilter(filter: Filter<number>, event: Event<number>): boole
return true return true
} }
export function matchFilters(filters: Filter<number>[], event: Event<number>): boolean { export function matchFilters(filters: Filter[], event: Event): boolean {
for (let i = 0; i < filters.length; i++) { for (let i = 0; i < filters.length; i++) {
if (matchFilter(filters[i], event)) return true if (matchFilter(filters[i], event)) return true
} }
return false return false
} }
export function mergeFilters(...filters: Filter<number>[]): Filter<number> { export function mergeFilters(...filters: Filter[]): Filter {
let result: Filter<number> = {} let result: Filter = {}
for (let i = 0; i < filters.length; i++) { for (let i = 0; i < filters.length; i++) {
let filter = filters[i] let filter = filters[i]
Object.entries(filter).forEach(([property, values]) => { Object.entries(filter).forEach(([property, values]) => {

View File

@ -39,6 +39,11 @@ export const EventDeletion = 5
export const Repost = 6 export const Repost = 6
export const Reaction = 7 export const Reaction = 7
export const BadgeAward = 8 export const BadgeAward = 8
export const ChannelCreation = 40
export const ChannelMetadata = 41
export const ChannelMessage = 42
export const ChannelHideMessage = 43
export const ChannelMuteUser = 44
export const Report = 1984 export const Report = 1984
export const ZapRequest = 9734 export const ZapRequest = 9734
export const Zap = 9735 export const Zap = 9735
@ -68,10 +73,10 @@ export const BlockedRelaysList = 10006
export const SearchRelaysList = 10007 export const SearchRelaysList = 10007
export const InterestsList = 10015 export const InterestsList = 10015
export const UserEmojiList = 10030 export const UserEmojiList = 10030
export const WalletInfo = 13194 export const NWCWalletInfo = 13194
export const LightningPubRPC = 21000 export const LightningPubRPC = 21000
export const WalletRequest = 23194 export const NWCWalletRequest = 23194
export const WalletResponse = 23195 export const NWCWalletResponse = 23195
export const NostrConnect = 24133 export const NostrConnect = 24133
export const HTTPAuth = 27235 export const HTTPAuth = 27235
export const Followsets = 30000 export const Followsets = 30000

View File

@ -23,10 +23,10 @@ export function getPow(hex: string): number {
* *
* Adapted from Snort: https://git.v0l.io/Kieran/snort/src/commit/4df6c19248184218c4c03728d61e94dae5f2d90c/packages/system/src/pow-util.ts#L14-L36 * Adapted from Snort: https://git.v0l.io/Kieran/snort/src/commit/4df6c19248184218c4c03728d61e94dae5f2d90c/packages/system/src/pow-util.ts#L14-L36
*/ */
export function minePow<K extends number>(unsigned: UnsignedEvent<K>, difficulty: number): Omit<Event<K>, 'sig'> { export function minePow(unsigned: UnsignedEvent, difficulty: number): Omit<Event, 'sig'> {
let count = 0 let count = 0
const event = unsigned as Omit<Event<K>, 'sig'> const event = unsigned as Omit<Event, 'sig'>
const tag = ['nonce', count.toString(), difficulty.toString()] const tag = ['nonce', count.toString(), difficulty.toString()]
event.tags.push(tag) event.tags.push(tag)

View File

@ -1,5 +1,6 @@
import { finishEvent, Kind } from './event.ts' import { finishEvent } from './event.ts'
import { getPublicKey } from './keys.ts' import { getPublicKey } from './keys.ts'
import { Repost, ShortTextNote } from './kinds.ts'
import { finishRepostEvent, getRepostedEventPointer, getRepostedEvent } from './nip18.ts' import { finishRepostEvent, getRepostedEventPointer, getRepostedEvent } from './nip18.ts'
import { buildEvent } from './test-helpers.ts' import { buildEvent } from './test-helpers.ts'
@ -12,7 +13,7 @@ describe('finishRepostEvent + getRepostedEventPointer + getRepostedEvent', () =>
const repostedEvent = finishEvent( const repostedEvent = finishEvent(
{ {
kind: Kind.Text, kind: ShortTextNote,
tags: [ tags: [
['e', 'replied event id'], ['e', 'replied event id'],
['p', 'replied event pubkey'], ['p', 'replied event pubkey'],
@ -30,7 +31,7 @@ describe('finishRepostEvent + getRepostedEventPointer + getRepostedEvent', () =>
const event = finishRepostEvent(template, repostedEvent, relayUrl, privateKey) const event = finishRepostEvent(template, repostedEvent, relayUrl, privateKey)
expect(event.kind).toEqual(Kind.Repost) expect(event.kind).toEqual(Repost)
expect(event.tags).toEqual([ expect(event.tags).toEqual([
['e', repostedEvent.id, relayUrl], ['e', repostedEvent.id, relayUrl],
['p', repostedEvent.pubkey], ['p', repostedEvent.pubkey],
@ -61,7 +62,7 @@ describe('finishRepostEvent + getRepostedEventPointer + getRepostedEvent', () =>
const event = finishRepostEvent(template, repostedEvent, relayUrl, privateKey) const event = finishRepostEvent(template, repostedEvent, relayUrl, privateKey)
expect(event.kind).toEqual(Kind.Repost) expect(event.kind).toEqual(Repost)
expect(event.tags).toEqual([ expect(event.tags).toEqual([
['nonstandard', 'tag'], ['nonstandard', 'tag'],
['e', repostedEvent.id, relayUrl], ['e', repostedEvent.id, relayUrl],
@ -88,7 +89,7 @@ describe('finishRepostEvent + getRepostedEventPointer + getRepostedEvent', () =>
describe('getRepostedEventPointer', () => { describe('getRepostedEventPointer', () => {
it('should parse an event with only an `e` tag', () => { it('should parse an event with only an `e` tag', () => {
const event = buildEvent({ const event = buildEvent({
kind: Kind.Repost, kind: Repost,
tags: [['e', 'reposted event id', relayUrl]], tags: [['e', 'reposted event id', relayUrl]],
}) })

View File

@ -1,4 +1,5 @@
import { Event, finishEvent, Kind, verifySignature } from './event.ts' import { Event, finishEvent, verifySignature } from './event.ts'
import { Repost } from './kinds.ts'
import { EventPointer } from './nip19.ts' import { EventPointer } from './nip19.ts'
export type RepostEventTemplate = { export type RepostEventTemplate = {
@ -20,13 +21,13 @@ export type RepostEventTemplate = {
export function finishRepostEvent( export function finishRepostEvent(
t: RepostEventTemplate, t: RepostEventTemplate,
reposted: Event<number>, reposted: Event,
relayUrl: string, relayUrl: string,
privateKey: string, privateKey: string,
): Event<Kind.Repost> { ): Event {
return finishEvent( return finishEvent(
{ {
kind: Kind.Repost, kind: Repost,
tags: [...(t.tags ?? []), ['e', reposted.id, relayUrl], ['p', reposted.pubkey]], tags: [...(t.tags ?? []), ['e', reposted.id, relayUrl], ['p', reposted.pubkey]],
content: t.content === '' ? '' : JSON.stringify(reposted), content: t.content === '' ? '' : JSON.stringify(reposted),
created_at: t.created_at, created_at: t.created_at,
@ -35,8 +36,8 @@ export function finishRepostEvent(
) )
} }
export function getRepostedEventPointer(event: Event<number>): undefined | EventPointer { export function getRepostedEventPointer(event: Event): undefined | EventPointer {
if (event.kind !== Kind.Repost) { if (event.kind !== Repost) {
return undefined return undefined
} }
@ -69,20 +70,17 @@ export type GetRepostedEventOptions = {
skipVerification?: boolean skipVerification?: boolean
} }
export function getRepostedEvent( export function getRepostedEvent(event: Event, { skipVerification }: GetRepostedEventOptions = {}): undefined | Event {
event: Event<number>,
{ skipVerification }: GetRepostedEventOptions = {},
): undefined | Event<number> {
const pointer = getRepostedEventPointer(event) const pointer = getRepostedEventPointer(event)
if (pointer === undefined || event.content === '') { if (pointer === undefined || event.content === '') {
return undefined return undefined
} }
let repostedEvent: undefined | Event<number> let repostedEvent: undefined | Event
try { try {
repostedEvent = JSON.parse(event.content) as Event<number> repostedEvent = JSON.parse(event.content) as Event
} catch (error) { } catch (error) {
return undefined return undefined
} }

View File

@ -1,5 +1,6 @@
import { finishEvent, Kind } from './event.ts' import { finishEvent } from './event.ts'
import { getPublicKey } from './keys.ts' import { getPublicKey } from './keys.ts'
import { Reaction, ShortTextNote } from './kinds.ts'
import { finishReactionEvent, getReactedEventPointer } from './nip25.ts' import { finishReactionEvent, getReactedEventPointer } from './nip25.ts'
describe('finishReactionEvent + getReactedEventPointer', () => { describe('finishReactionEvent + getReactedEventPointer', () => {
@ -9,7 +10,7 @@ describe('finishReactionEvent + getReactedEventPointer', () => {
const reactedEvent = finishEvent( const reactedEvent = finishEvent(
{ {
kind: Kind.Text, kind: ShortTextNote,
tags: [ tags: [
['e', 'replied event id'], ['e', 'replied event id'],
['p', 'replied event pubkey'], ['p', 'replied event pubkey'],
@ -27,7 +28,7 @@ describe('finishReactionEvent + getReactedEventPointer', () => {
const event = finishReactionEvent(template, reactedEvent, privateKey) const event = finishReactionEvent(template, reactedEvent, privateKey)
expect(event.kind).toEqual(Kind.Reaction) expect(event.kind).toEqual(Reaction)
expect(event.tags).toEqual([ expect(event.tags).toEqual([
['e', 'replied event id'], ['e', 'replied event id'],
['p', 'replied event pubkey'], ['p', 'replied event pubkey'],
@ -55,7 +56,7 @@ describe('finishReactionEvent + getReactedEventPointer', () => {
const event = finishReactionEvent(template, reactedEvent, privateKey) const event = finishReactionEvent(template, reactedEvent, privateKey)
expect(event.kind).toEqual(Kind.Reaction) expect(event.kind).toEqual(Reaction)
expect(event.tags).toEqual([ expect(event.tags).toEqual([
['nonstandard', 'tag'], ['nonstandard', 'tag'],
['e', 'replied event id'], ['e', 'replied event id'],

View File

@ -1,4 +1,5 @@
import { Event, finishEvent, Kind } from './event.ts' import { Event, finishEvent } from './event.ts'
import { Reaction } from './kinds.ts'
import type { EventPointer } from './nip19.ts' import type { EventPointer } from './nip19.ts'
@ -16,17 +17,13 @@ export type ReactionEventTemplate = {
created_at: number created_at: number
} }
export function finishReactionEvent( export function finishReactionEvent(t: ReactionEventTemplate, reacted: Event, privateKey: string): Event {
t: ReactionEventTemplate,
reacted: Event<number>,
privateKey: string,
): Event<Kind.Reaction> {
const inheritedTags = reacted.tags.filter(tag => tag.length >= 2 && (tag[0] === 'e' || tag[0] === 'p')) const inheritedTags = reacted.tags.filter(tag => tag.length >= 2 && (tag[0] === 'e' || tag[0] === 'p'))
return finishEvent( return finishEvent(
{ {
...t, ...t,
kind: Kind.Reaction, kind: Reaction,
tags: [...(t.tags ?? []), ...inheritedTags, ['e', reacted.id], ['p', reacted.pubkey]], tags: [...(t.tags ?? []), ...inheritedTags, ['e', reacted.id], ['p', reacted.pubkey]],
content: t.content ?? '+', content: t.content ?? '+',
}, },
@ -34,8 +31,8 @@ export function finishReactionEvent(
) )
} }
export function getReactedEventPointer(event: Event<number>): undefined | EventPointer { export function getReactedEventPointer(event: Event): undefined | EventPointer {
if (event.kind !== Kind.Reaction) { if (event.kind !== Reaction) {
return undefined return undefined
} }

View File

@ -1,5 +1,5 @@
import { Kind } from './event.ts'
import { getPublicKey } from './keys.ts' import { getPublicKey } from './keys.ts'
import * as Kind from './kinds.ts'
import { import {
channelCreateEvent, channelCreateEvent,
channelMetadataEvent, channelMetadataEvent,

View File

@ -1,4 +1,5 @@
import { Event, finishEvent, Kind } from './event.ts' import { Event, finishEvent } from './event.ts'
import { ChannelCreation, ChannelHideMessage, ChannelMessage, ChannelMetadata, ChannelMuteUser } from './kinds.ts'
export interface ChannelMetadata { export interface ChannelMetadata {
name: string name: string
@ -44,10 +45,7 @@ export interface ChannelMuteUserEventTemplate {
tags?: string[][] tags?: string[][]
} }
export const channelCreateEvent = ( export const channelCreateEvent = (t: ChannelCreateEventTemplate, privateKey: string): Event | undefined => {
t: ChannelCreateEventTemplate,
privateKey: string,
): Event<Kind.ChannelCreation> | undefined => {
let content: string let content: string
if (typeof t.content === 'object') { if (typeof t.content === 'object') {
content = JSON.stringify(t.content) content = JSON.stringify(t.content)
@ -59,7 +57,7 @@ export const channelCreateEvent = (
return finishEvent( return finishEvent(
{ {
kind: Kind.ChannelCreation, kind: ChannelCreation,
tags: [...(t.tags ?? [])], tags: [...(t.tags ?? [])],
content: content, content: content,
created_at: t.created_at, created_at: t.created_at,
@ -68,10 +66,7 @@ export const channelCreateEvent = (
) )
} }
export const channelMetadataEvent = ( export const channelMetadataEvent = (t: ChannelMetadataEventTemplate, privateKey: string): Event | undefined => {
t: ChannelMetadataEventTemplate,
privateKey: string,
): Event<Kind.ChannelMetadata> | undefined => {
let content: string let content: string
if (typeof t.content === 'object') { if (typeof t.content === 'object') {
content = JSON.stringify(t.content) content = JSON.stringify(t.content)
@ -83,7 +78,7 @@ export const channelMetadataEvent = (
return finishEvent( return finishEvent(
{ {
kind: Kind.ChannelMetadata, kind: ChannelMetadata,
tags: [['e', t.channel_create_event_id], ...(t.tags ?? [])], tags: [['e', t.channel_create_event_id], ...(t.tags ?? [])],
content: content, content: content,
created_at: t.created_at, created_at: t.created_at,
@ -92,7 +87,7 @@ export const channelMetadataEvent = (
) )
} }
export const channelMessageEvent = (t: ChannelMessageEventTemplate, privateKey: string): Event<Kind.ChannelMessage> => { export const channelMessageEvent = (t: ChannelMessageEventTemplate, privateKey: string): Event => {
const tags = [['e', t.channel_create_event_id, t.relay_url, 'root']] const tags = [['e', t.channel_create_event_id, t.relay_url, 'root']]
if (t.reply_to_channel_message_event_id) { if (t.reply_to_channel_message_event_id) {
@ -101,7 +96,7 @@ export const channelMessageEvent = (t: ChannelMessageEventTemplate, privateKey:
return finishEvent( return finishEvent(
{ {
kind: Kind.ChannelMessage, kind: ChannelMessage,
tags: [...tags, ...(t.tags ?? [])], tags: [...tags, ...(t.tags ?? [])],
content: t.content, content: t.content,
created_at: t.created_at, created_at: t.created_at,
@ -111,10 +106,7 @@ export const channelMessageEvent = (t: ChannelMessageEventTemplate, privateKey:
} }
/* "e" tag should be the kind 42 event to hide */ /* "e" tag should be the kind 42 event to hide */
export const channelHideMessageEvent = ( export const channelHideMessageEvent = (t: ChannelHideMessageEventTemplate, privateKey: string): Event | undefined => {
t: ChannelHideMessageEventTemplate,
privateKey: string,
): Event<Kind.ChannelHideMessage> | undefined => {
let content: string let content: string
if (typeof t.content === 'object') { if (typeof t.content === 'object') {
content = JSON.stringify(t.content) content = JSON.stringify(t.content)
@ -126,7 +118,7 @@ export const channelHideMessageEvent = (
return finishEvent( return finishEvent(
{ {
kind: Kind.ChannelHideMessage, kind: ChannelHideMessage,
tags: [['e', t.channel_message_event_id], ...(t.tags ?? [])], tags: [['e', t.channel_message_event_id], ...(t.tags ?? [])],
content: content, content: content,
created_at: t.created_at, created_at: t.created_at,
@ -135,10 +127,7 @@ export const channelHideMessageEvent = (
) )
} }
export const channelMuteUserEvent = ( export const channelMuteUserEvent = (t: ChannelMuteUserEventTemplate, privateKey: string): Event | undefined => {
t: ChannelMuteUserEventTemplate,
privateKey: string,
): Event<Kind.ChannelMuteUser> | undefined => {
let content: string let content: string
if (typeof t.content === 'object') { if (typeof t.content === 'object') {
content = JSON.stringify(t.content) content = JSON.stringify(t.content)
@ -150,7 +139,7 @@ export const channelMuteUserEvent = (
return finishEvent( return finishEvent(
{ {
kind: Kind.ChannelMuteUser, kind: ChannelMuteUser,
tags: [['p', t.pubkey_to_mute], ...(t.tags ?? [])], tags: [['p', t.pubkey_to_mute], ...(t.tags ?? [])],
content: content, content: content,
created_at: t.created_at, created_at: t.created_at,

View File

@ -1,4 +1,5 @@
import { Kind, type EventTemplate, type Event } from './event.ts' import { type EventTemplate, type Event } from './event.ts'
import { ClientAuth } from './kinds.ts'
import { Relay } from './relay.ts' import { Relay } from './relay.ts'
/** /**
@ -17,10 +18,10 @@ export const authenticate = async ({
}: { }: {
challenge: string challenge: string
relay: Relay relay: Relay
sign: <K extends number = number>(e: EventTemplate<K>) => Promise<Event<K>> | Event<K> sign: (e: EventTemplate) => Promise<Event> | Event
}): Promise<void> => { }): Promise<void> => {
const e: EventTemplate = { const evt: EventTemplate = {
kind: Kind.ClientAuth, kind: ClientAuth,
created_at: Math.floor(Date.now() / 1000), created_at: Math.floor(Date.now() / 1000),
tags: [ tags: [
['relay', relay.url], ['relay', relay.url],
@ -28,5 +29,5 @@ export const authenticate = async ({
], ],
content: '', content: '',
} }
return relay.auth(await sign(e)) return relay.auth(await sign(evt))
} }

View File

@ -1,7 +1,7 @@
import { makeNwcRequestEvent, parseConnectionString } from './nip47' import { makeNwcRequestEvent, parseConnectionString } from './nip47'
import { Kind } from './event'
import { decrypt } from './nip04.ts' import { decrypt } from './nip04.ts'
import crypto from 'node:crypto' import crypto from 'node:crypto'
import { NWCWalletRequest } from './kinds.ts'
// @ts-ignore // @ts-ignore
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
@ -53,7 +53,7 @@ describe('makeNwcRequestEvent', () => {
invoice, invoice,
}) })
const timeAfter = Date.now() / 1000 const timeAfter = Date.now() / 1000
expect(result.kind).toBe(Kind.NwcRequest) expect(result.kind).toBe(NWCWalletRequest)
expect(result.created_at).toBeGreaterThan(timeBefore) expect(result.created_at).toBeGreaterThan(timeBefore)
expect(result.created_at).toBeLessThan(timeAfter) expect(result.created_at).toBeLessThan(timeAfter)
expect(await decrypt(secret, pubkey, result.content)).toEqual( expect(await decrypt(secret, pubkey, result.content)).toEqual(

View File

@ -1,6 +1,6 @@
import { finishEvent } from './event.ts' import { finishEvent } from './event.ts'
import { NWCWalletRequest } from './kinds.ts'
import { encrypt } from './nip04.ts' import { encrypt } from './nip04.ts'
import { Kind } from './event'
export function parseConnectionString(connectionString: string) { export function parseConnectionString(connectionString: string) {
const { pathname, searchParams } = new URL(connectionString) const { pathname, searchParams } = new URL(connectionString)
@ -32,7 +32,7 @@ export async function makeNwcRequestEvent({
} }
const encryptedContent = await encrypt(secret, pubkey, JSON.stringify(content)) const encryptedContent = await encrypt(secret, pubkey, JSON.stringify(content))
const eventTemplate = { const eventTemplate = {
kind: Kind.NwcRequest, kind: NWCWalletRequest,
created_at: Math.round(Date.now() / 1000), created_at: Math.round(Date.now() / 1000),
content: encryptedContent, content: encryptedContent,
tags: [['p', pubkey]], tags: [['p', pubkey]],

View File

@ -1,6 +1,6 @@
import { bech32 } from '@scure/base' import { bech32 } from '@scure/base'
import { Kind, validateEvent, verifySignature, type Event, type EventTemplate } from './event.ts' import { validateEvent, verifySignature, type Event, type EventTemplate } from './event.ts'
import { utf8Decoder } from './utils.ts' import { utf8Decoder } from './utils.ts'
var _fetch: any var _fetch: any
@ -13,7 +13,7 @@ export function useFetchImplementation(fetchImplementation: any) {
_fetch = fetchImplementation _fetch = fetchImplementation
} }
export async function getZapEndpoint(metadata: Event<Kind.Metadata>): Promise<null | string> { export async function getZapEndpoint(metadata: Event): Promise<null | string> {
try { try {
let lnurl: string = '' let lnurl: string = ''
let { lud06, lud16 } = JSON.parse(metadata.content) let { lud06, lud16 } = JSON.parse(metadata.content)
@ -53,11 +53,11 @@ export function makeZapRequest({
amount: number amount: number
comment: string comment: string
relays: string[] relays: string[]
}): EventTemplate<Kind.ZapRequest> { }): EventTemplate {
if (!amount) throw new Error('amount not given') if (!amount) throw new Error('amount not given')
if (!profile) throw new Error('profile not given') if (!profile) throw new Error('profile not given')
let zr: EventTemplate<Kind.ZapRequest> = { let zr: EventTemplate = {
kind: 9734, kind: 9734,
created_at: Math.round(Date.now() / 1000), created_at: Math.round(Date.now() / 1000),
content: comment, content: comment,
@ -111,11 +111,11 @@ export function makeZapReceipt({
preimage?: string preimage?: string
bolt11: string bolt11: string
paidAt: Date paidAt: Date
}): EventTemplate<Kind.Zap> { }): EventTemplate {
let zr: Event<Kind.ZapRequest> = JSON.parse(zapRequest) let zr: Event = JSON.parse(zapRequest)
let tagsFromZapRequest = zr.tags.filter(([t]) => t === 'e' || t === 'p' || t === 'a') let tagsFromZapRequest = zr.tags.filter(([t]) => t === 'e' || t === 'p' || t === 'a')
let zap: EventTemplate<Kind.Zap> = { let zap: EventTemplate = {
kind: 9735, kind: 9735,
created_at: Math.round(paidAt.getTime() / 1000), created_at: Math.round(paidAt.getTime() / 1000),
content: '', content: '',

View File

@ -1,9 +1,10 @@
import { getToken, unpackEventFromToken, validateEvent, validateToken } from './nip98.ts' import { getToken, unpackEventFromToken, validateEvent, validateToken } from './nip98.ts'
import { Event, Kind, finishEvent } from './event.ts' import { Event, finishEvent } from './event.ts'
import { generatePrivateKey, getPublicKey } from './keys.ts' import { generatePrivateKey, getPublicKey } from './keys.ts'
import { sha256 } from '@noble/hashes/sha256' import { sha256 } from '@noble/hashes/sha256'
import { utf8Encoder } from './utils.ts' import { utf8Encoder } from './utils.ts'
import { bytesToHex } from '@noble/hashes/utils' import { bytesToHex } from '@noble/hashes/utils'
import { HTTPAuth } from './kinds.ts'
const sk = generatePrivateKey() const sk = generatePrivateKey()
@ -15,7 +16,7 @@ describe('getToken', () => {
expect(decodedResult.created_at).toBeGreaterThan(0) expect(decodedResult.created_at).toBeGreaterThan(0)
expect(decodedResult.content).toBe('') expect(decodedResult.content).toBe('')
expect(decodedResult.kind).toBe(Kind.HttpAuth) expect(decodedResult.kind).toBe(HTTPAuth)
expect(decodedResult.pubkey).toBe(getPublicKey(sk)) expect(decodedResult.pubkey).toBe(getPublicKey(sk))
expect(decodedResult.tags).toStrictEqual([ expect(decodedResult.tags).toStrictEqual([
['u', 'http://test.com'], ['u', 'http://test.com'],
@ -30,7 +31,7 @@ describe('getToken', () => {
expect(decodedResult.created_at).toBeGreaterThan(0) expect(decodedResult.created_at).toBeGreaterThan(0)
expect(decodedResult.content).toBe('') expect(decodedResult.content).toBe('')
expect(decodedResult.kind).toBe(Kind.HttpAuth) expect(decodedResult.kind).toBe(HTTPAuth)
expect(decodedResult.pubkey).toBe(getPublicKey(sk)) expect(decodedResult.pubkey).toBe(getPublicKey(sk))
expect(decodedResult.tags).toStrictEqual([ expect(decodedResult.tags).toStrictEqual([
['u', 'http://test.com'], ['u', 'http://test.com'],
@ -49,7 +50,7 @@ describe('getToken', () => {
expect(decodedResult.created_at).toBeGreaterThan(0) expect(decodedResult.created_at).toBeGreaterThan(0)
expect(decodedResult.content).toBe('') expect(decodedResult.content).toBe('')
expect(decodedResult.kind).toBe(Kind.HttpAuth) expect(decodedResult.kind).toBe(HTTPAuth)
expect(decodedResult.pubkey).toBe(getPublicKey(sk)) expect(decodedResult.pubkey).toBe(getPublicKey(sk))
expect(decodedResult.tags).toStrictEqual([ expect(decodedResult.tags).toStrictEqual([
['u', 'http://test.com'], ['u', 'http://test.com'],
@ -76,7 +77,7 @@ describe('getToken', () => {
expect(decodedResult.created_at).toBeGreaterThan(0) expect(decodedResult.created_at).toBeGreaterThan(0)
expect(decodedResult.content).toBe('') expect(decodedResult.content).toBe('')
expect(decodedResult.kind).toBe(Kind.HttpAuth) expect(decodedResult.kind).toBe(HTTPAuth)
expect(decodedResult.pubkey).toBe(getPublicKey(sk)) expect(decodedResult.pubkey).toBe(getPublicKey(sk))
expect(decodedResult.tags).toStrictEqual([ expect(decodedResult.tags).toStrictEqual([
['u', 'http://test.com'], ['u', 'http://test.com'],

View File

@ -1,12 +1,13 @@
import { bytesToHex } from '@noble/hashes/utils' import { bytesToHex } from '@noble/hashes/utils'
import { sha256 } from '@noble/hashes/sha256' import { sha256 } from '@noble/hashes/sha256'
import { base64 } from '@scure/base' import { base64 } from '@scure/base'
import { Event, EventTemplate, Kind, getBlankEvent, verifySignature } from './event' import { Event, EventTemplate, verifySignature } from './event'
import { utf8Decoder, utf8Encoder } from './utils' import { utf8Decoder, utf8Encoder } from './utils'
import { HTTPAuth } from './kinds'
const _authorizationScheme = 'Nostr ' const _authorizationScheme = 'Nostr '
function hashPayload(payload: any): string { export function hashPayload(payload: any): string {
const hash = sha256(utf8Encoder.encode(JSON.stringify(payload))) const hash = sha256(utf8Encoder.encode(JSON.stringify(payload)))
return bytesToHex(hash) return bytesToHex(hash)
} }
@ -21,28 +22,29 @@ function hashPayload(payload: any): string {
export async function getToken( export async function getToken(
loginUrl: string, loginUrl: string,
httpMethod: string, httpMethod: string,
sign: <K extends number = number>(e: EventTemplate<K>) => Promise<Event<K>> | Event<K>, sign: (e: EventTemplate) => Promise<Event> | Event,
includeAuthorizationScheme: boolean = false, includeAuthorizationScheme: boolean = false,
payload?: Record<string, any>, payload?: Record<string, any>,
): Promise<string> { ): Promise<string> {
if (!loginUrl || !httpMethod) throw new Error('Missing loginUrl or httpMethod') if (!loginUrl || !httpMethod) throw new Error('Missing loginUrl or httpMethod')
const event = getBlankEvent(Kind.HttpAuth) const event: EventTemplate = {
kind: HTTPAuth,
event.tags = [ tags: [
['u', loginUrl], ['u', loginUrl],
['method', httpMethod], ['method', httpMethod],
] ],
created_at: Math.round(new Date().getTime() / 1000),
content: '',
}
if (payload) { if (payload) {
event.tags.push(['payload', bytesToHex(sha256(utf8Encoder.encode(JSON.stringify(payload))))]) event.tags.push(['payload', bytesToHex(sha256(utf8Encoder.encode(JSON.stringify(payload))))])
} }
event.created_at = Math.round(new Date().getTime() / 1000)
const signedEvent = await sign(event) const signedEvent = await sign(event)
const authorizationScheme = includeAuthorizationScheme ? _authorizationScheme : '' const authorizationScheme = includeAuthorizationScheme ? _authorizationScheme : ''
return authorizationScheme + base64.encode(utf8Encoder.encode(JSON.stringify(signedEvent))) return authorizationScheme + base64.encode(utf8Encoder.encode(JSON.stringify(signedEvent)))
} }
@ -86,7 +88,7 @@ export async function validateEvent(event: Event, url: string, method: string, b
if (!verifySignature(event)) { if (!verifySignature(event)) {
throw new Error('Invalid nostr event, signature invalid') throw new Error('Invalid nostr event, signature invalid')
} }
if (event.kind !== Kind.HttpAuth) { if (event.kind !== HTTPAuth) {
throw new Error('Invalid nostr event, kind invalid') throw new Error('Invalid nostr event, kind invalid')
} }

32
pool.ts
View File

@ -5,10 +5,10 @@ import type { Event } from './event.ts'
import { matchFilters, mergeFilters, type Filter } from './filter.ts' import { matchFilters, mergeFilters, type Filter } from './filter.ts'
type BatchedRequest = { type BatchedRequest = {
filters: Filter<any>[] filters: Filter[]
relays: string[] relays: string[]
resolve: (events: Event<any>[]) => void resolve: (events: Event[]) => void
events: Event<any>[] events: Event[]
} }
export class SimplePool { export class SimplePool {
@ -58,7 +58,7 @@ export class SimplePool {
return relay return relay
} }
sub<K extends number = number>(relays: string[], filters: Filter<K>[], opts?: SubscriptionOptions): Sub<K> { sub(relays: string[], filters: Filter[], opts?: SubscriptionOptions): Sub {
let _knownIds: Set<string> = new Set() let _knownIds: Set<string> = new Set()
let modifiedOpts = { ...(opts || {}) } let modifiedOpts = { ...(opts || {}) }
modifiedOpts.alreadyHaveEvent = (id, url) => { modifiedOpts.alreadyHaveEvent = (id, url) => {
@ -118,7 +118,7 @@ export class SimplePool {
} }
}) })
let greaterSub: Sub<K> = { let greaterSub: Sub = {
sub(filters, opts) { sub(filters, opts) {
subs.forEach(sub => sub.sub(filters, opts)) subs.forEach(sub => sub.sub(filters, opts))
return greaterSub as any return greaterSub as any
@ -146,11 +146,7 @@ export class SimplePool {
return greaterSub return greaterSub
} }
get<K extends number = number>( get(relays: string[], filter: Filter, opts?: SubscriptionOptions): Promise<Event | null> {
relays: string[],
filter: Filter<K>,
opts?: SubscriptionOptions,
): Promise<Event<K> | null> {
return new Promise(resolve => { return new Promise(resolve => {
let sub = this.sub(relays, [filter], opts) let sub = this.sub(relays, [filter], opts)
let timeout = setTimeout(() => { let timeout = setTimeout(() => {
@ -165,13 +161,9 @@ export class SimplePool {
}) })
} }
list<K extends number = number>( list(relays: string[], filters: Filter[], opts?: SubscriptionOptions): Promise<Event[]> {
relays: string[],
filters: Filter<K>[],
opts?: SubscriptionOptions,
): Promise<Event<K>[]> {
return new Promise(resolve => { return new Promise(resolve => {
let events: Event<K>[] = [] let events: Event[] = []
let sub = this.sub(relays, filters, opts) let sub = this.sub(relays, filters, opts)
sub.on('event', event => { sub.on('event', event => {
@ -186,11 +178,7 @@ export class SimplePool {
}) })
} }
batchedList<K extends number = number>( batchedList(batchKey: string, relays: string[], filters: Filter[]): Promise<Event[]> {
batchKey: string,
relays: string[],
filters: Filter<K>[],
): Promise<Event<K>[]> {
return new Promise(resolve => { return new Promise(resolve => {
if (!this.batchedByKey[batchKey]) { if (!this.batchedByKey[batchKey]) {
this.batchedByKey[batchKey] = [ this.batchedByKey[batchKey] = [
@ -236,7 +224,7 @@ export class SimplePool {
}) })
} }
publish(relays: string[], event: Event<number>): Promise<void>[] { publish(relays: string[], event: Event): Promise<void>[] {
return relays.map(async relay => { return relays.map(async relay => {
let r = await this.ensureRelay(relay) let r = await this.ensureRelay(relay)
return r.publish(event) return r.publish(event)

View File

@ -15,8 +15,8 @@ type RelayEvent = {
export type CountPayload = { export type CountPayload = {
count: number count: number
} }
export type SubEvent<K extends number> = { export type SubEvent = {
event: (event: Event<K>) => void | Promise<void> event: (event: Event) => void | Promise<void>
count: (payload: CountPayload) => void | Promise<void> count: (payload: CountPayload) => void | Promise<void>
eose: () => void | Promise<void> eose: () => void | Promise<void>
} }
@ -25,21 +25,21 @@ export type Relay = {
status: number status: number
connect: () => Promise<void> connect: () => Promise<void>
close: () => void close: () => void
sub: <K extends number = number>(filters: Filter<K>[], opts?: SubscriptionOptions) => Sub<K> sub: (filters: Filter[], opts?: SubscriptionOptions) => Sub
list: <K extends number = number>(filters: Filter<K>[], opts?: SubscriptionOptions) => Promise<Event<K>[]> list: (filters: Filter[], opts?: SubscriptionOptions) => Promise<Event[]>
get: <K extends number = number>(filter: Filter<K>, opts?: SubscriptionOptions) => Promise<Event<K> | null> get: (filter: Filter, opts?: SubscriptionOptions) => Promise<Event | null>
count: (filters: Filter[], opts?: SubscriptionOptions) => Promise<CountPayload | null> count: (filters: Filter[], opts?: SubscriptionOptions) => Promise<CountPayload | null>
publish: (event: Event<number>) => Promise<void> publish: (event: Event) => Promise<void>
auth: (event: Event<number>) => Promise<void> auth: (event: Event) => Promise<void>
off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(event: T, listener: U) => void off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(event: T, listener: U) => void
on: <T extends keyof RelayEvent, U extends RelayEvent[T]>(event: T, listener: U) => void on: <T extends keyof RelayEvent, U extends RelayEvent[T]>(event: T, listener: U) => void
} }
export type Sub<K extends number = number> = { export type Sub = {
sub: <K extends number = number>(filters: Filter<K>[], opts: SubscriptionOptions) => Sub<K> sub: (filters: Filter[], opts: SubscriptionOptions) => Sub
unsub: () => void unsub: () => void
on: <T extends keyof SubEvent<K>, U extends SubEvent<K>[T]>(event: T, listener: U) => void on: <T extends keyof SubEvent, U extends SubEvent[T]>(event: T, listener: U) => void
off: <T extends keyof SubEvent<K>, U extends SubEvent<K>[T]>(event: T, listener: U) => void off: <T extends keyof SubEvent, U extends SubEvent[T]>(event: T, listener: U) => void
events: AsyncGenerator<Event<K>, void, unknown> events: AsyncGenerator<Event, void, unknown>
} }
export type SubscriptionOptions = { export type SubscriptionOptions = {
@ -72,7 +72,7 @@ export function relayInit(
var openSubs: { [id: string]: { filters: Filter[] } & SubscriptionOptions } = {} var openSubs: { [id: string]: { filters: Filter[] } & SubscriptionOptions } = {}
var listeners = newListeners() var listeners = newListeners()
var subListeners: { var subListeners: {
[subid: string]: { [TK in keyof SubEvent<any>]: SubEvent<any>[TK][] } [subid: string]: { [TK in keyof SubEvent]: SubEvent[TK][] }
} = {} } = {}
var pubListeners: { var pubListeners: {
[eventid: string]: { [eventid: string]: {
@ -223,15 +223,15 @@ export function relayInit(
} }
} }
const sub = <K extends number = number>( const sub = (
filters: Filter<K>[], filters: Filter[],
{ {
verb = 'REQ', verb = 'REQ',
skipVerification = false, skipVerification = false,
alreadyHaveEvent = null, alreadyHaveEvent = null,
id = Math.random().toString().slice(2), id = Math.random().toString().slice(2),
}: SubscriptionOptions = {}, }: SubscriptionOptions = {},
): Sub<K> => { ): Sub => {
let subid = id let subid = id
openSubs[subid] = { openSubs[subid] = {
@ -242,7 +242,7 @@ export function relayInit(
} }
trySend([verb, subid, ...filters]) trySend([verb, subid, ...filters])
let subscription: Sub<K> = { let subscription: Sub = {
sub: (newFilters, newOpts = {}) => sub: (newFilters, newOpts = {}) =>
sub(newFilters || filters, { sub(newFilters || filters, {
skipVerification: newOpts.skipVerification || skipVerification, skipVerification: newOpts.skipVerification || skipVerification,
@ -275,7 +275,7 @@ export function relayInit(
return subscription return subscription
} }
function _publishEvent(event: Event<number>, type: string) { function _publishEvent(event: Event, type: string) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!event.id) { if (!event.id) {
reject(new Error(`event ${event} has no id`)) reject(new Error(`event ${event} has no id`))
@ -305,7 +305,7 @@ export function relayInit(
list: (filters, opts?: SubscriptionOptions) => list: (filters, opts?: SubscriptionOptions) =>
new Promise(resolve => { new Promise(resolve => {
let s = sub(filters, opts) let s = sub(filters, opts)
let events: Event<any>[] = [] let events: Event[] = []
let timeout = setTimeout(() => { let timeout = setTimeout(() => {
s.unsub() s.unsub()
resolve(events) resolve(events)
@ -366,11 +366,11 @@ export function relayInit(
} }
} }
export async function* eventsGenerator<K extends number>(sub: Sub<K>): AsyncGenerator<Event<K>, void, unknown> { export async function* eventsGenerator(sub: Sub): AsyncGenerator<Event, void, unknown> {
let nextResolve: ((event: Event<K>) => void) | undefined let nextResolve: ((event: Event) => void) | undefined
const eventQueue: Event<K>[] = [] const eventQueue: Event[] = []
const pushToQueue = (event: Event<K>) => { const pushToQueue = (event: Event) => {
if (nextResolve) { if (nextResolve) {
nextResolve(event) nextResolve(event)
nextResolve = undefined nextResolve = undefined
@ -386,7 +386,7 @@ export async function* eventsGenerator<K extends number>(sub: Sub<K>): AsyncGene
if (eventQueue.length > 0) { if (eventQueue.length > 0) {
yield eventQueue.shift()! yield eventQueue.shift()!
} else { } else {
const event = await new Promise<Event<K>>(resolve => { const event = await new Promise<Event>(resolve => {
nextResolve = resolve nextResolve = resolve
}) })
yield event yield event

View File

@ -1,12 +1,10 @@
import type { Event } from './event.ts' import type { Event } from './event.ts'
type EventParams<K extends number> = Partial<Event<K>>
/** Build an event for testing purposes. */ /** Build an event for testing purposes. */
export function buildEvent<K extends number = 1>(params: EventParams<K>): Event<K> { export function buildEvent(params: Partial<Event>): Event {
return { return {
id: '', id: '',
kind: 1 as K, kind: 1,
pubkey: '', pubkey: '',
created_at: 0, created_at: 0,
content: '', content: '',

View File

@ -16,7 +16,7 @@ export function normalizeURL(url: string): string {
// //
// fast insert-into-sorted-array functions adapted from https://github.com/terrymorse58/fast-sorted-array // fast insert-into-sorted-array functions adapted from https://github.com/terrymorse58/fast-sorted-array
// //
export function insertEventIntoDescendingList(sortedArray: Event<number>[], event: Event<number>) { export function insertEventIntoDescendingList(sortedArray: Event[], event: Event) {
let start = 0 let start = 0
let end = sortedArray.length - 1 let end = sortedArray.length - 1
let midPoint let midPoint
@ -54,7 +54,7 @@ export function insertEventIntoDescendingList(sortedArray: Event<number>[], even
return sortedArray return sortedArray
} }
export function insertEventIntoAscendingList(sortedArray: Event<number>[], event: Event<number>) { export function insertEventIntoAscendingList(sortedArray: Event[], event: Event) {
let start = 0 let start = 0
let end = sortedArray.length - 1 let end = sortedArray.length - 1
let midPoint let midPoint