mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-08 16:28:49 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b79d6a899 | ||
|
|
c1efbbd919 | ||
|
|
7d58705e9a | ||
|
|
f1d315632c | ||
|
|
348d118ce4 |
@@ -1,5 +1,5 @@
|
||||
import { describe, test, expect } from 'bun:test'
|
||||
import { matchFilter, matchFilters, mergeFilters } from './filter.ts'
|
||||
import { getFilterLimit, matchFilter, matchFilters, mergeFilters } from './filter.ts'
|
||||
import { buildEvent } from './test-helpers.ts'
|
||||
|
||||
describe('Filter', () => {
|
||||
@@ -241,4 +241,27 @@ describe('Filter', () => {
|
||||
).toEqual({ kinds: [1, 7, 9, 10], since: 10, until: 30 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('getFilterLimit', () => {
|
||||
test('should handle ids', () => {
|
||||
expect(getFilterLimit({ ids: ['123'] })).toEqual(1)
|
||||
expect(getFilterLimit({ ids: ['123'], limit: 2 })).toEqual(1)
|
||||
expect(getFilterLimit({ ids: ['123'], limit: 0 })).toEqual(0)
|
||||
expect(getFilterLimit({ ids: ['123'], limit: -1 })).toEqual(0)
|
||||
})
|
||||
|
||||
test('should count the authors times replaceable kinds', () => {
|
||||
expect(getFilterLimit({ kinds: [0], authors: ['alex'] })).toEqual(1)
|
||||
expect(getFilterLimit({ kinds: [0, 3], authors: ['alex'] })).toEqual(2)
|
||||
expect(getFilterLimit({ kinds: [0, 3], authors: ['alex', 'fiatjaf'] })).toEqual(4)
|
||||
})
|
||||
|
||||
test('should return Infinity for authors with regular kinds', () => {
|
||||
expect(getFilterLimit({ kinds: [1], authors: ['alex'] })).toEqual(Infinity)
|
||||
})
|
||||
|
||||
test('should return Infinity for empty filters', () => {
|
||||
expect(getFilterLimit({})).toEqual(Infinity)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
17
filter.ts
17
filter.ts
@@ -1,4 +1,5 @@
|
||||
import { Event } from './core.ts'
|
||||
import { isReplaceableKind } from './kinds.ts'
|
||||
|
||||
export type Filter = {
|
||||
ids?: string[]
|
||||
@@ -70,3 +71,19 @@ export function mergeFilters(...filters: Filter[]): Filter {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/** Calculate the intrinsic limit of a filter. This function may return `Infinity`. */
|
||||
export function getFilterLimit(filter: Filter): number {
|
||||
if (filter.ids && !filter.ids.length) return 0
|
||||
if (filter.kinds && !filter.kinds.length) return 0
|
||||
if (filter.authors && !filter.authors.length) return 0
|
||||
|
||||
return Math.min(
|
||||
Math.max(0, filter.limit ?? Infinity),
|
||||
filter.ids?.length ?? Infinity,
|
||||
filter.authors?.length &&
|
||||
filter.kinds?.every((kind) => isReplaceableKind(kind))
|
||||
? filter.authors.length * filter.kinds.length
|
||||
: Infinity,
|
||||
)
|
||||
}
|
||||
|
||||
18
kinds.ts
18
kinds.ts
@@ -35,27 +35,22 @@ export const ShortTextNote = 1
|
||||
export const RecommendRelay = 2
|
||||
export const Contacts = 3
|
||||
export const EncryptedDirectMessage = 4
|
||||
export const EncryptedDirectMessages = 4
|
||||
export const EventDeletion = 5
|
||||
export const Repost = 6
|
||||
export const Reaction = 7
|
||||
export const BadgeAward = 8
|
||||
export const GenericRepost = 16
|
||||
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 ZapRequest = 9734
|
||||
export const Zap = 9735
|
||||
export const RelayList = 10002
|
||||
export const ClientAuth = 22242
|
||||
export const BadgeDefinition = 30009
|
||||
export const FileMetadata = 1063
|
||||
export const EncryptedDirectMessages = 4
|
||||
export const GenericRepost = 16
|
||||
export const OpenTimestamps = 1040
|
||||
export const FileMetadata = 1063
|
||||
export const LiveChatMessage = 1311
|
||||
export const ProblemTracker = 1971
|
||||
export const Report = 1984
|
||||
export const Reporting = 1984
|
||||
export const Label = 1985
|
||||
export const CommunityPostApproval = 4550
|
||||
@@ -63,9 +58,12 @@ export const JobRequest = 5999
|
||||
export const JobResult = 6999
|
||||
export const JobFeedback = 7000
|
||||
export const ZapGoal = 9041
|
||||
export const ZapRequest = 9734
|
||||
export const Zap = 9735
|
||||
export const Highlights = 9802
|
||||
export const Mutelist = 10000
|
||||
export const Pinlist = 10001
|
||||
export const RelayList = 10002
|
||||
export const BookmarkList = 10003
|
||||
export const CommunitiesList = 10004
|
||||
export const PublicChatsList = 10005
|
||||
@@ -75,6 +73,7 @@ export const InterestsList = 10015
|
||||
export const UserEmojiList = 10030
|
||||
export const NWCWalletInfo = 13194
|
||||
export const LightningPubRPC = 21000
|
||||
export const ClientAuth = 22242
|
||||
export const NWCWalletRequest = 23194
|
||||
export const NWCWalletResponse = 23195
|
||||
export const NostrConnect = 24133
|
||||
@@ -85,6 +84,7 @@ export const Relaysets = 30002
|
||||
export const Bookmarksets = 30003
|
||||
export const Curationsets = 30004
|
||||
export const ProfileBadges = 30008
|
||||
export const BadgeDefinition = 30009
|
||||
export const Interestsets = 30015
|
||||
export const CreateOrUpdateStall = 30017
|
||||
export const CreateOrUpdateProduct = 30018
|
||||
|
||||
44
nip40.test.ts
Normal file
44
nip40.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { describe, test, expect, jest } from 'bun:test'
|
||||
import { buildEvent } from './test-helpers.ts'
|
||||
import { getExpiration, isEventExpired, waitForExpire, onExpire } from './nip40.ts'
|
||||
|
||||
describe('getExpiration', () => {
|
||||
test('returns the expiration as a Date object', () => {
|
||||
const event = buildEvent({ tags: [['expiration', '123']] })
|
||||
const result = getExpiration(event)
|
||||
expect(result).toEqual(new Date(123000))
|
||||
})
|
||||
})
|
||||
|
||||
describe('isEventExpired', () => {
|
||||
test('returns true when the event has expired', () => {
|
||||
const event = buildEvent({ tags: [['expiration', '123']] })
|
||||
const result = isEventExpired(event)
|
||||
expect(result).toEqual(true)
|
||||
})
|
||||
|
||||
test('returns false when the event has not expired', () => {
|
||||
const future = Math.floor(Date.now() / 1000) + 10
|
||||
const event = buildEvent({ tags: [['expiration', future.toString()]] })
|
||||
const result = isEventExpired(event)
|
||||
expect(result).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('waitForExpire', () => {
|
||||
test('returns a promise that resolves when the event expires', async () => {
|
||||
const event = buildEvent({ tags: [['expiration', '123']] })
|
||||
const result = await waitForExpire(event)
|
||||
expect(result).toEqual(event)
|
||||
})
|
||||
})
|
||||
|
||||
describe('onExpire', () => {
|
||||
test('calls the callback when the event expires', async () => {
|
||||
const event = buildEvent({ tags: [['expiration', '123']] })
|
||||
const callback = jest.fn()
|
||||
onExpire(event, callback)
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
expect(callback).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
49
nip40.ts
Normal file
49
nip40.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Event } from './core.ts'
|
||||
|
||||
/** Get the expiration of the event as a `Date` object, if any. */
|
||||
function getExpiration(event: Event): Date | undefined {
|
||||
const tag = event.tags.find(([name]) => name === 'expiration')
|
||||
if (tag) {
|
||||
return new Date(parseInt(tag[1]) * 1000)
|
||||
}
|
||||
}
|
||||
|
||||
/** Check if the event has expired. */
|
||||
function isEventExpired(event: Event): boolean {
|
||||
const expiration = getExpiration(event)
|
||||
if (expiration) {
|
||||
return Date.now() > expiration.getTime()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a promise that resolves when the event expires. */
|
||||
async function waitForExpire(event: Event): Promise<Event> {
|
||||
const expiration = getExpiration(event)
|
||||
if (expiration) {
|
||||
const diff = expiration.getTime() - Date.now()
|
||||
if (diff > 0) {
|
||||
await sleep(diff)
|
||||
return event
|
||||
} else {
|
||||
return event
|
||||
}
|
||||
} else {
|
||||
throw new Error('Event has no expiration')
|
||||
}
|
||||
}
|
||||
|
||||
/** Calls the callback when the event expires. */
|
||||
function onExpire(event: Event, callback: (event: Event) => void): void {
|
||||
waitForExpire(event)
|
||||
.then(callback)
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
/** Resolves when the given number of milliseconds have elapsed. */
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
export { getExpiration, isEventExpired, waitForExpire, onExpire }
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "nostr-tools",
|
||||
"version": "2.1.2",
|
||||
"version": "2.1.3",
|
||||
"description": "Tools for making a Nostr client.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -173,8 +173,9 @@
|
||||
"@noble/hashes": "1.3.1",
|
||||
"@scure/base": "1.1.1",
|
||||
"@scure/bip32": "1.3.1",
|
||||
"@scure/bip39": "1.2.1",
|
||||
"mitata": "^0.1.6",
|
||||
"@scure/bip39": "1.2.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"nostr-wasm": "v0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -205,6 +206,7 @@
|
||||
"eslint-plugin-babel": "^5.3.1",
|
||||
"esm-loader-typescript": "^1.0.3",
|
||||
"events": "^3.3.0",
|
||||
"mitata": "^0.1.6",
|
||||
"node-fetch": "^2.6.9",
|
||||
"prettier": "^3.0.3",
|
||||
"tsd": "^0.22.0",
|
||||
|
||||
@@ -19,7 +19,7 @@ test('removing duplicates when subscribing', async () => {
|
||||
pool.subscribeMany(relays, [{ authors: [pub] }], {
|
||||
onevent(event: Event) {
|
||||
// this should be called only once even though we're listening
|
||||
// to multiple relays because the events will be catched and
|
||||
// to multiple relays because the events will be caught and
|
||||
// deduplicated efficiently (without even being parsed)
|
||||
received.push(event)
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user