Compare commits

...

5 Commits

Author SHA1 Message Date
fiatjaf
7a50d9328d add some more kinds. 2025-12-09 10:56:43 -03:00
fiatjaf
65412e5b85 NostrEvent > Event. 2025-12-06 12:21:56 -03:00
fiatjaf
ca36ae9530 update README with new enableReconnect() behavior. 2025-12-05 21:59:52 -03:00
fiatjaf
0b6543e1a8 use setInterval() instead of nested setTimeout()s for pingpong.
prevent call stacks from building up
2025-12-05 21:46:16 -03:00
fiatjaf
693b262b7c CLOSED answers to dummyReq are also ok. 2025-12-05 21:03:34 -03:00
6 changed files with 69 additions and 40 deletions

View File

@@ -160,16 +160,7 @@ Using both `enablePing: true` and `enableReconnect: true` is recommended as it w
const pool = new SimplePool({ enablePing: true, enableReconnect: true }) const pool = new SimplePool({ enablePing: true, enableReconnect: true })
``` ```
The `enableReconnect` option can also be a callback function which will receive the current subscription filters and should return a new set of filters. This is useful if you want to modify the subscription on reconnect, for example, to update the `since` parameter to fetch only new events. When reconnecting, all existing subscriptions will have their filters automatically updated with `since:` set to the timestamp of the last event received on them `+1`, then restarted.
```js
const pool = new SimplePool({
enableReconnect: (filters) => {
const newSince = Math.floor(Date.now() / 1000)
return filters.map(filter => ({ ...filter, since: newSince }))
}
})
```
### Parsing references (mentions) from a content based on NIP-27 ### Parsing references (mentions) from a content based on NIP-27

View File

@@ -37,7 +37,7 @@ export class AbstractRelay {
public baseEoseTimeout: number = 4400 public baseEoseTimeout: number = 4400
public connectionTimeout: number = 4400 public connectionTimeout: number = 4400
public publishTimeout: number = 4400 public publishTimeout: number = 4400
public pingFrequency: number = 20000 public pingFrequency: number = 29000
public pingTimeout: number = 20000 public pingTimeout: number = 20000
public resubscribeBackoff: number[] = [10000, 10000, 10000, 20000, 20000, 30000, 60000] public resubscribeBackoff: number[] = [10000, 10000, 10000, 20000, 20000, 30000, 60000]
public openSubs: Map<string, Subscription> = new Map() public openSubs: Map<string, Subscription> = new Map()
@@ -45,7 +45,7 @@ export class AbstractRelay {
public enableReconnect: boolean public enableReconnect: boolean
private connectionTimeoutHandle: ReturnType<typeof setTimeout> | undefined private connectionTimeoutHandle: ReturnType<typeof setTimeout> | undefined
private reconnectTimeoutHandle: ReturnType<typeof setTimeout> | undefined private reconnectTimeoutHandle: ReturnType<typeof setTimeout> | undefined
private pingTimeoutHandle: ReturnType<typeof setTimeout> | undefined private pingIntervalHandle: ReturnType<typeof setInterval> | undefined
private reconnectAttempts: number = 0 private reconnectAttempts: number = 0
private closedIntentionally: boolean = false private closedIntentionally: boolean = false
@@ -111,9 +111,9 @@ export class AbstractRelay {
} }
private handleHardClose(reason: string) { private handleHardClose(reason: string) {
if (this.pingTimeoutHandle) { if (this.pingIntervalHandle) {
clearTimeout(this.pingTimeoutHandle) clearInterval(this.pingIntervalHandle)
this.pingTimeoutHandle = undefined this.pingIntervalHandle = undefined
} }
this._connected = false this._connected = false
@@ -177,7 +177,7 @@ export class AbstractRelay {
} }
if (this.enablePing) { if (this.enablePing) {
this.pingpong() this.pingIntervalHandle = setInterval(() => this.pingpong(), this.pingFrequency)
} }
resolve() resolve()
} }
@@ -209,20 +209,30 @@ export class AbstractRelay {
}) })
} }
private async waitForDummyReq() { private waitForDummyReq() {
return new Promise((resolve, _) => { return new Promise((resolve, reject) => {
if (!this.connectionPromise) return reject(new Error(`no connection to ${this.url}, can't ping`))
// make a dummy request with expected empty eose reply // make a dummy request with expected empty eose reply
// ["REQ", "_", {"ids":["aaaa...aaaa"], "limit": 0}] // ["REQ", "_", {"ids":["aaaa...aaaa"], "limit": 0}]
const sub = this.subscribe( try {
[{ ids: ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], limit: 0 }], const sub = this.subscribe(
{ [{ ids: ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], limit: 0 }],
oneose: () => { {
sub.close() oneose: () => {
resolve(true) resolve(true)
sub.close()
},
onclose() {
// if we get a CLOSED it's because the relay is alive
resolve(true)
},
eoseTimeout: this.pingTimeout + 1000,
}, },
eoseTimeout: this.pingTimeout + 1000, )
}, } catch (err) {
) reject(err)
}
}) })
} }
@@ -237,10 +247,8 @@ export class AbstractRelay {
this.ws && this.ws.ping && (this.ws as any).once ? this.waitForPingPong() : this.waitForDummyReq(), this.ws && this.ws.ping && (this.ws as any).once ? this.waitForPingPong() : this.waitForDummyReq(),
new Promise(res => setTimeout(() => res(false), this.pingTimeout)), new Promise(res => setTimeout(() => res(false), this.pingTimeout)),
]) ])
if (result) {
// schedule another pingpong if (!result) {
this.pingTimeoutHandle = setTimeout(() => this.pingpong(), this.pingFrequency)
} else {
// pingpong closing socket // pingpong closing socket
if (this.ws?.readyState === this._WebSocket.OPEN) { if (this.ws?.readyState === this._WebSocket.OPEN) {
this.ws?.close() this.ws?.close()
@@ -448,9 +456,9 @@ export class AbstractRelay {
clearTimeout(this.reconnectTimeoutHandle) clearTimeout(this.reconnectTimeoutHandle)
this.reconnectTimeoutHandle = undefined this.reconnectTimeoutHandle = undefined
} }
if (this.pingTimeoutHandle) { if (this.pingIntervalHandle) {
clearTimeout(this.pingTimeoutHandle) clearInterval(this.pingIntervalHandle)
this.pingTimeoutHandle = undefined this.pingIntervalHandle = undefined
} }
this.closeAllSubscriptions('relay connection closed by us') this.closeAllSubscriptions('relay connection closed by us')
this._connected = false this._connected = false

View File

@@ -8,7 +8,7 @@ export interface Nostr {
/** Designates a verified event signature. */ /** Designates a verified event signature. */
export const verifiedSymbol = Symbol('verified') export const verifiedSymbol = Symbol('verified')
export interface Event { export type NostrEvent = {
kind: number kind: number
tags: string[][] tags: string[][]
content: string content: string
@@ -19,7 +19,7 @@ export interface Event {
[verifiedSymbol]?: boolean [verifiedSymbol]?: boolean
} }
export type NostrEvent = Event export type Event = NostrEvent
export type EventTemplate = Pick<Event, 'kind' | 'tags' | 'content' | 'created_at'> export type EventTemplate = Pick<Event, 'kind' | 'tags' | 'content' | 'created_at'>
export type UnsignedEvent = Pick<Event, 'kind' | 'tags' | 'content' | 'created_at' | 'pubkey'> export type UnsignedEvent = Pick<Event, 'kind' | 'tags' | 'content' | 'created_at' | 'pubkey'>

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nostr/tools", "name": "@nostr/tools",
"version": "2.19.1", "version": "2.19.3",
"exports": { "exports": {
".": "./index.ts", ".": "./index.ts",
"./core": "./core.ts", "./core": "./core.ts",

View File

@@ -55,12 +55,24 @@ export const Reaction = 7
export type Reaction = typeof Reaction export type Reaction = typeof Reaction
export const BadgeAward = 8 export const BadgeAward = 8
export type BadgeAward = typeof BadgeAward export type BadgeAward = typeof BadgeAward
export const ChatMessage = 9
export type ChatMessage = typeof ChatMessage
export const ForumThread = 11
export type ForumThread = typeof ForumThread
export const Seal = 13 export const Seal = 13
export type Seal = typeof Seal export type Seal = typeof Seal
export const PrivateDirectMessage = 14 export const PrivateDirectMessage = 14
export type PrivateDirectMessage = typeof PrivateDirectMessage export type PrivateDirectMessage = typeof PrivateDirectMessage
export const FileMessage = 15
export type FileMessage = typeof FileMessage
export const GenericRepost = 16 export const GenericRepost = 16
export type GenericRepost = typeof GenericRepost export type GenericRepost = typeof GenericRepost
export const Photo = 20
export type Photo = typeof Photo
export const NormalVideo = 21
export type NormalVideo = typeof NormalVideo
export const ShortVideo = 22
export type ShortVideo = typeof ShortVideo
export const ChannelCreation = 40 export const ChannelCreation = 40
export type ChannelCreation = typeof ChannelCreation export type ChannelCreation = typeof ChannelCreation
export const ChannelMetadata = 41 export const ChannelMetadata = 41
@@ -75,10 +87,18 @@ export const OpenTimestamps = 1040
export type OpenTimestamps = typeof OpenTimestamps export type OpenTimestamps = typeof OpenTimestamps
export const GiftWrap = 1059 export const GiftWrap = 1059
export type GiftWrap = typeof GiftWrap export type GiftWrap = typeof GiftWrap
export const Poll = 1068
export type Poll = typeof Poll
export const FileMetadata = 1063 export const FileMetadata = 1063
export type FileMetadata = typeof FileMetadata export type FileMetadata = typeof FileMetadata
export const Comment = 1111
export type Comment = typeof Comment
export const LiveChatMessage = 1311 export const LiveChatMessage = 1311
export type LiveChatMessage = typeof LiveChatMessage export type LiveChatMessage = typeof LiveChatMessage
export const Voice = 1222
export type Voice = typeof Voice
export const VoiceComment = 1244
export type VoiceComment = typeof VoiceComment
export const ProblemTracker = 1971 export const ProblemTracker = 1971
export type ProblemTracker = typeof ProblemTracker export type ProblemTracker = typeof ProblemTracker
export const Report = 1984 export const Report = 1984
@@ -103,6 +123,8 @@ export const Zap = 9735
export type Zap = typeof Zap export type Zap = typeof Zap
export const Highlights = 9802 export const Highlights = 9802
export type Highlights = typeof Highlights export type Highlights = typeof Highlights
export const PollResponse = 1018
export type PollResponse = typeof PollResponse
export const Mutelist = 10000 export const Mutelist = 10000
export type Mutelist = typeof Mutelist export type Mutelist = typeof Mutelist
export const Pinlist = 10001 export const Pinlist = 10001
@@ -119,6 +141,8 @@ export const BlockedRelaysList = 10006
export type BlockedRelaysList = typeof BlockedRelaysList export type BlockedRelaysList = typeof BlockedRelaysList
export const SearchRelaysList = 10007 export const SearchRelaysList = 10007
export type SearchRelaysList = typeof SearchRelaysList export type SearchRelaysList = typeof SearchRelaysList
export const FavoriteRelays = 10012
export type FavoriteRelays = typeof FavoriteRelays
export const InterestsList = 10015 export const InterestsList = 10015
export type InterestsList = typeof InterestsList export type InterestsList = typeof InterestsList
export const UserEmojiList = 10030 export const UserEmojiList = 10030
@@ -127,6 +151,8 @@ export const DirectMessageRelaysList = 10050
export type DirectMessageRelaysList = typeof DirectMessageRelaysList export type DirectMessageRelaysList = typeof DirectMessageRelaysList
export const FileServerPreference = 10096 export const FileServerPreference = 10096
export type FileServerPreference = typeof FileServerPreference export type FileServerPreference = typeof FileServerPreference
export const BlossomServerList = 10063
export type BlossomServerList = typeof BlossomServerList
export const NWCWalletInfo = 13194 export const NWCWalletInfo = 13194
export type NWCWalletInfo = typeof NWCWalletInfo export type NWCWalletInfo = typeof NWCWalletInfo
export const LightningPubRPC = 21000 export const LightningPubRPC = 21000
@@ -185,9 +211,13 @@ export const Calendar = 31924
export type Calendar = typeof Calendar export type Calendar = typeof Calendar
export const CalendarEventRSVP = 31925 export const CalendarEventRSVP = 31925
export type CalendarEventRSVP = typeof CalendarEventRSVP export type CalendarEventRSVP = typeof CalendarEventRSVP
export const RelayReview = 31987
export type RelayReview = typeof RelayReview
export const Handlerrecommendation = 31989 export const Handlerrecommendation = 31989
export type Handlerrecommendation = typeof Handlerrecommendation export type Handlerrecommendation = typeof Handlerrecommendation
export const Handlerinformation = 31990 export const Handlerinformation = 31990
export type Handlerinformation = typeof Handlerinformation export type Handlerinformation = typeof Handlerinformation
export const CommunityDefinition = 34550 export const CommunityDefinition = 34550
export type CommunityDefinition = typeof CommunityDefinition export type CommunityDefinition = typeof CommunityDefinition
export const GroupMetadata = 39000
export type GroupMetadata = typeof GroupMetadata

View File

@@ -1,7 +1,7 @@
{ {
"type": "module", "type": "module",
"name": "nostr-tools", "name": "nostr-tools",
"version": "2.19.1", "version": "2.19.3",
"description": "Tools for making a Nostr client.", "description": "Tools for making a Nostr client.",
"repository": { "repository": {
"type": "git", "type": "git",