Compare commits

...

6 Commits

Author SHA1 Message Date
tf 282b41d2a6
Merge 57f297be52 into 23aebbd341 2025-09-09 18:50:57 -05:00
Anderson Juhasc 23aebbd341 update NIP-27 example in README 2025-08-27 10:32:45 -03:00
Anderson Juhasc a3fcd79545 ensures consistency for .jpg/.JPG, .mp4/.MP4, etc 2025-08-27 10:32:45 -03:00
tajava2006 0e6e7af934 chore: Bump version and document NIP-46 usage 2025-08-25 11:00:06 -03:00
codytseng 8866042edf relay: ensure onclose callback is triggered 2025-08-24 22:22:38 -03:00
tf 57f297be52 Fix Relay subscribe() method generates malformed JSON when filters are empty 2025-07-19 18:31:12 +07:00
6 changed files with 24 additions and 42 deletions

View File

@ -169,8 +169,10 @@ for (let block of nip27.parse(evt.content)) {
case 'video': case 'video':
case 'audio': case 'audio':
console.log("it's a media url:", block.url) console.log("it's a media url:", block.url)
break
case 'relay': case 'relay':
console.log("it's a websocket url, probably a relay address:", block.url) console.log("it's a websocket url, probably a relay address:", block.url)
break
default: default:
break break
} }
@ -191,7 +193,7 @@ const localSecretKey = generateSecretKey()
### Method 1: Using a Bunker URI (`bunker://`) ### Method 1: Using a Bunker URI (`bunker://`)
This is the bunker-initiated flow. Your client receives a `bunker://` string or a NIP-05 identifier from the user. You use `BunkerSigner.fromBunker()` to create an instance, which returns immediately. You must then explicitly call `await bunker.connect()` to establish the connection with the bunker. This is the bunker-initiated flow. Your client receives a `bunker://` string or a NIP-05 identifier from the user. You use `BunkerSigner.fromBunker()` to create an instance, which returns immediately. For the **initial connection** with a new bunker, you must explicitly call `await bunker.connect()` to establish the connection and receive authorization.
```js ```js
import { BunkerSigner, parseBunkerInput } from '@nostr/tools/nip46' import { BunkerSigner, parseBunkerInput } from '@nostr/tools/nip46'
@ -221,6 +223,7 @@ const event = await bunker.signEvent({
await signer.close() await signer.close()
pool.close([]) pool.close([])
``` ```
> **Note on Reconnecting:** Once a connection has been successfully established and the `BunkerPointer` is stored, you do **not** need to call `await bunker.connect()` on subsequent sessions.
### Method 2: Using a Client-generated URI (`nostrconnect://`) ### Method 2: Using a Client-generated URI (`nostrconnect://`)
@ -260,6 +263,7 @@ const event = await signer.signEvent({
await signer.close() await signer.close()
pool.close([]) pool.close([])
``` ```
> **Note on Persistence:** This method is ideal for the initial sign-in. To allow users to stay logged in across sessions, you should store the connection details and use `Method 1` for subsequent reconnections.
### Parsing thread from any note based on NIP-10 ### Parsing thread from any note based on NIP-10

View File

@ -121,23 +121,19 @@ export class AbstractRelay {
this.ws.onerror = ev => { this.ws.onerror = ev => {
clearTimeout(this.connectionTimeoutHandle) clearTimeout(this.connectionTimeoutHandle)
reject((ev as any).message || 'websocket error') reject((ev as any).message || 'websocket error')
if (this._connected) { this._connected = false
this._connected = false this.connectionPromise = undefined
this.connectionPromise = undefined this.onclose?.()
this.onclose?.() this.closeAllSubscriptions('relay connection errored')
this.closeAllSubscriptions('relay connection errored')
}
} }
this.ws.onclose = ev => { this.ws.onclose = ev => {
clearTimeout(this.connectionTimeoutHandle) clearTimeout(this.connectionTimeoutHandle)
reject((ev as any).message || 'websocket closed') reject((ev as any).message || 'websocket closed')
if (this._connected) { this._connected = false
this._connected = false this.connectionPromise = undefined
this.connectionPromise = undefined this.onclose?.()
this.onclose?.() this.closeAllSubscriptions('relay connection closed')
this.closeAllSubscriptions('relay connection closed')
}
} }
this.ws.onmessage = this._onmessage.bind(this) this.ws.onmessage = this._onmessage.bind(this)
@ -187,8 +183,8 @@ export class AbstractRelay {
// pingpong closing socket // pingpong closing socket
this.closeAllSubscriptions('pingpong timed out') this.closeAllSubscriptions('pingpong timed out')
this._connected = false this._connected = false
this.ws?.close()
this.onclose?.() this.onclose?.()
this.ws?.close()
} }
} }
} }
@ -378,8 +374,8 @@ export class AbstractRelay {
public close() { public close() {
this.closeAllSubscriptions('relay connection closed by us') this.closeAllSubscriptions('relay connection closed by us')
this._connected = false this._connected = false
this.ws?.close()
this.onclose?.() this.onclose?.()
this.ws?.close()
} }
// this is the function assigned to this.ws.onmessage // this is the function assigned to this.ws.onmessage
@ -430,7 +426,7 @@ export class Subscription {
} }
public fire() { public fire() {
this.relay.send('["REQ","' + this.id + '",' + JSON.stringify(this.filters).substring(1)) this.relay.send(JSON.stringify(['REQ', this.id, ...this.filters]))
// only now we start counting the eoseTimeout // only now we start counting the eoseTimeout
this.eoseTimeoutHandle = setTimeout(this.receivedEose.bind(this), this.eoseTimeout) this.eoseTimeoutHandle = setTimeout(this.receivedEose.bind(this), this.eoseTimeout)

View File

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

View File

@ -90,35 +90,19 @@ export function* parse(content: string): Iterable<Block> {
yield { type: 'text', text: content.substring(prevIndex, u - prefixLen) } yield { type: 'text', text: content.substring(prevIndex, u - prefixLen) }
} }
if ( if (/\.(png|jpe?g|gif|webp)$/i.test(url.pathname)) {
url.pathname.endsWith('.png') ||
url.pathname.endsWith('.jpg') ||
url.pathname.endsWith('.jpeg') ||
url.pathname.endsWith('.gif') ||
url.pathname.endsWith('.webp')
) {
yield { type: 'image', url: url.toString() } yield { type: 'image', url: url.toString() }
index = end index = end
prevIndex = index prevIndex = index
continue continue
} }
if ( if (/\.(mp4|avi|webm|mkv)$/i.test(url.pathname)) {
url.pathname.endsWith('.mp4') ||
url.pathname.endsWith('.avi') ||
url.pathname.endsWith('.webm') ||
url.pathname.endsWith('.mkv')
) {
yield { type: 'video', url: url.toString() } yield { type: 'video', url: url.toString() }
index = end index = end
prevIndex = index prevIndex = index
continue continue
} }
if ( if (/\.(mp3|aac|ogg|opus)$/i.test(url.pathname)) {
url.pathname.endsWith('.mp3') ||
url.pathname.endsWith('.aac') ||
url.pathname.endsWith('.ogg') ||
url.pathname.endsWith('.opus')
) {
yield { type: 'audio', url: url.toString() } yield { type: 'audio', url: url.toString() }
index = end index = end
prevIndex = index prevIndex = index

View File

@ -97,7 +97,7 @@ export type ParsedNostrConnectURI = {
name?: string name?: string
url?: string url?: string
image?: string image?: string
}; }
originalString: string originalString: string
} }
@ -185,7 +185,6 @@ export function parseNostrConnectURI(uri: string): ParsedNostrConnectURI {
} }
} }
export type BunkerSignerParams = { export type BunkerSignerParams = {
pool?: AbstractSimplePool pool?: AbstractSimplePool
onauth?: (url: string) => void onauth?: (url: string) => void
@ -236,7 +235,7 @@ export class BunkerSigner implements Signer {
public static fromBunker( public static fromBunker(
clientSecretKey: Uint8Array, clientSecretKey: Uint8Array,
bp: BunkerPointer, bp: BunkerPointer,
params: BunkerSignerParams = {} params: BunkerSignerParams = {},
): BunkerSigner { ): BunkerSigner {
if (bp.relays.length === 0) { if (bp.relays.length === 0) {
throw new Error('No relays specified for this bunker') throw new Error('No relays specified for this bunker')
@ -304,12 +303,11 @@ export class BunkerSigner implements Signer {
reject(new Error('Subscription closed before connection was established.')) reject(new Error('Subscription closed before connection was established.'))
}, },
maxWait, maxWait,
} },
) )
}) })
} }
private setupSubscription(params: BunkerSignerParams) { private setupSubscription(params: BunkerSignerParams) {
const listeners = this.listeners const listeners = this.listeners
const waitingForAuth = this.waitingForAuth const waitingForAuth = this.waitingForAuth

View File

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