mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-09 00:28:51 +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 { 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'
|
import { buildEvent } from './test-helpers.ts'
|
||||||
|
|
||||||
describe('Filter', () => {
|
describe('Filter', () => {
|
||||||
@@ -241,4 +241,27 @@ describe('Filter', () => {
|
|||||||
).toEqual({ kinds: [1, 7, 9, 10], since: 10, until: 30 })
|
).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 { Event } from './core.ts'
|
||||||
|
import { isReplaceableKind } from './kinds.ts'
|
||||||
|
|
||||||
export type Filter = {
|
export type Filter = {
|
||||||
ids?: string[]
|
ids?: string[]
|
||||||
@@ -70,3 +71,19 @@ export function mergeFilters(...filters: Filter[]): Filter {
|
|||||||
|
|
||||||
return result
|
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 RecommendRelay = 2
|
||||||
export const Contacts = 3
|
export const Contacts = 3
|
||||||
export const EncryptedDirectMessage = 4
|
export const EncryptedDirectMessage = 4
|
||||||
|
export const EncryptedDirectMessages = 4
|
||||||
export const EventDeletion = 5
|
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 GenericRepost = 16
|
||||||
export const ChannelCreation = 40
|
export const ChannelCreation = 40
|
||||||
export const ChannelMetadata = 41
|
export const ChannelMetadata = 41
|
||||||
export const ChannelMessage = 42
|
export const ChannelMessage = 42
|
||||||
export const ChannelHideMessage = 43
|
export const ChannelHideMessage = 43
|
||||||
export const ChannelMuteUser = 44
|
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 OpenTimestamps = 1040
|
||||||
|
export const FileMetadata = 1063
|
||||||
export const LiveChatMessage = 1311
|
export const LiveChatMessage = 1311
|
||||||
export const ProblemTracker = 1971
|
export const ProblemTracker = 1971
|
||||||
|
export const Report = 1984
|
||||||
export const Reporting = 1984
|
export const Reporting = 1984
|
||||||
export const Label = 1985
|
export const Label = 1985
|
||||||
export const CommunityPostApproval = 4550
|
export const CommunityPostApproval = 4550
|
||||||
@@ -63,9 +58,12 @@ export const JobRequest = 5999
|
|||||||
export const JobResult = 6999
|
export const JobResult = 6999
|
||||||
export const JobFeedback = 7000
|
export const JobFeedback = 7000
|
||||||
export const ZapGoal = 9041
|
export const ZapGoal = 9041
|
||||||
|
export const ZapRequest = 9734
|
||||||
|
export const Zap = 9735
|
||||||
export const Highlights = 9802
|
export const Highlights = 9802
|
||||||
export const Mutelist = 10000
|
export const Mutelist = 10000
|
||||||
export const Pinlist = 10001
|
export const Pinlist = 10001
|
||||||
|
export const RelayList = 10002
|
||||||
export const BookmarkList = 10003
|
export const BookmarkList = 10003
|
||||||
export const CommunitiesList = 10004
|
export const CommunitiesList = 10004
|
||||||
export const PublicChatsList = 10005
|
export const PublicChatsList = 10005
|
||||||
@@ -75,6 +73,7 @@ export const InterestsList = 10015
|
|||||||
export const UserEmojiList = 10030
|
export const UserEmojiList = 10030
|
||||||
export const NWCWalletInfo = 13194
|
export const NWCWalletInfo = 13194
|
||||||
export const LightningPubRPC = 21000
|
export const LightningPubRPC = 21000
|
||||||
|
export const ClientAuth = 22242
|
||||||
export const NWCWalletRequest = 23194
|
export const NWCWalletRequest = 23194
|
||||||
export const NWCWalletResponse = 23195
|
export const NWCWalletResponse = 23195
|
||||||
export const NostrConnect = 24133
|
export const NostrConnect = 24133
|
||||||
@@ -85,6 +84,7 @@ export const Relaysets = 30002
|
|||||||
export const Bookmarksets = 30003
|
export const Bookmarksets = 30003
|
||||||
export const Curationsets = 30004
|
export const Curationsets = 30004
|
||||||
export const ProfileBadges = 30008
|
export const ProfileBadges = 30008
|
||||||
|
export const BadgeDefinition = 30009
|
||||||
export const Interestsets = 30015
|
export const Interestsets = 30015
|
||||||
export const CreateOrUpdateStall = 30017
|
export const CreateOrUpdateStall = 30017
|
||||||
export const CreateOrUpdateProduct = 30018
|
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",
|
"type": "module",
|
||||||
"name": "nostr-tools",
|
"name": "nostr-tools",
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"description": "Tools for making a Nostr client.",
|
"description": "Tools for making a Nostr client.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -173,8 +173,9 @@
|
|||||||
"@noble/hashes": "1.3.1",
|
"@noble/hashes": "1.3.1",
|
||||||
"@scure/base": "1.1.1",
|
"@scure/base": "1.1.1",
|
||||||
"@scure/bip32": "1.3.1",
|
"@scure/bip32": "1.3.1",
|
||||||
"@scure/bip39": "1.2.1",
|
"@scure/bip39": "1.2.1"
|
||||||
"mitata": "^0.1.6",
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
"nostr-wasm": "v0.1.0"
|
"nostr-wasm": "v0.1.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -205,6 +206,7 @@
|
|||||||
"eslint-plugin-babel": "^5.3.1",
|
"eslint-plugin-babel": "^5.3.1",
|
||||||
"esm-loader-typescript": "^1.0.3",
|
"esm-loader-typescript": "^1.0.3",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
|
"mitata": "^0.1.6",
|
||||||
"node-fetch": "^2.6.9",
|
"node-fetch": "^2.6.9",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"tsd": "^0.22.0",
|
"tsd": "^0.22.0",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ test('removing duplicates when subscribing', async () => {
|
|||||||
pool.subscribeMany(relays, [{ authors: [pub] }], {
|
pool.subscribeMany(relays, [{ authors: [pub] }], {
|
||||||
onevent(event: Event) {
|
onevent(event: Event) {
|
||||||
// this should be called only once even though we're listening
|
// 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)
|
// deduplicated efficiently (without even being parsed)
|
||||||
received.push(event)
|
received.push(event)
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user