@@ -87,31 +87,7 @@ export type NostrConnectParams = {
8787 image ?: string
8888}
8989
90- export type ParsedNostrConnectURI = {
91- protocol : 'nostrconnect'
92- clientPubkey : string
93- params : {
94- relays : string [ ]
95- secret : string
96- perms ?: string [ ]
97- name ?: string
98- url ?: string
99- image ?: string
100- }
101- originalString : string
102- }
103-
10490export function createNostrConnectURI ( params : NostrConnectParams ) : string {
105- if ( ! params . clientPubkey ) {
106- throw new Error ( 'clientPubkey is required.' )
107- }
108- if ( ! params . relays || params . relays . length === 0 ) {
109- throw new Error ( 'At least one relay is required.' )
110- }
111- if ( ! params . secret ) {
112- throw new Error ( 'secret is required.' )
113- }
114-
11591 const queryParams = new URLSearchParams ( )
11692
11793 params . relays . forEach ( relay => {
@@ -136,55 +112,6 @@ export function createNostrConnectURI(params: NostrConnectParams): string {
136112 return `nostrconnect://${ params . clientPubkey } ?${ queryParams . toString ( ) } `
137113}
138114
139- export function parseNostrConnectURI ( uri : string ) : ParsedNostrConnectURI {
140- if ( ! uri . startsWith ( 'nostrconnect://' ) ) {
141- throw new Error ( 'Invalid nostrconnect URI: Must start with "nostrconnect://".' )
142- }
143-
144- const [ protocolAndPubkey , queryString ] = uri . split ( '?' )
145- if ( ! protocolAndPubkey || ! queryString ) {
146- throw new Error ( 'Invalid nostrconnect URI: Missing query string.' )
147- }
148-
149- const clientPubkey = protocolAndPubkey . substring ( 'nostrconnect://' . length )
150- if ( ! clientPubkey ) {
151- throw new Error ( 'Invalid nostrconnect URI: Missing client-pubkey.' )
152- }
153-
154- const queryParams = new URLSearchParams ( queryString )
155-
156- const relays = queryParams . getAll ( 'relay' )
157- if ( relays . length === 0 ) {
158- throw new Error ( 'Invalid nostrconnect URI: Missing "relay" parameter.' )
159- }
160-
161- const secret = queryParams . get ( 'secret' )
162- if ( ! secret ) {
163- throw new Error ( 'Invalid nostrconnect URI: Missing "secret" parameter.' )
164- }
165-
166- const permsString = queryParams . get ( 'perms' )
167- const perms = permsString ? permsString . split ( ',' ) : undefined
168-
169- const name = queryParams . get ( 'name' ) || undefined
170- const url = queryParams . get ( 'url' ) || undefined
171- const image = queryParams . get ( 'image' ) || undefined
172-
173- return {
174- protocol : 'nostrconnect' ,
175- clientPubkey,
176- params : {
177- relays,
178- secret,
179- perms,
180- name,
181- url,
182- image,
183- } ,
184- originalString : uri ,
185- }
186- }
187-
188115export type BunkerSignerParams = {
189116 pool ?: AbstractSimplePool
190117 onauth ?: ( url : string ) => void
@@ -238,15 +165,15 @@ export class BunkerSigner implements Signer {
238165 params : BunkerSignerParams = { } ,
239166 ) : BunkerSigner {
240167 if ( bp . relays . length === 0 ) {
241- throw new Error ( 'No relays specified for this bunker' )
168+ throw new Error ( 'no relays specified for this bunker' )
242169 }
243170
244171 const signer = new BunkerSigner ( clientSecretKey , params )
245172
246173 signer . conversationKey = getConversationKey ( clientSecretKey , bp . pubkey )
247174 signer . bp = bp
248175
249- signer . setupSubscription ( params )
176+ signer . setupSubscription ( )
250177 return signer
251178 }
252179
@@ -257,22 +184,22 @@ export class BunkerSigner implements Signer {
257184 public static async fromURI (
258185 clientSecretKey : Uint8Array ,
259186 connectionURI : string ,
260- params : BunkerSignerParams = { } ,
261- maxWait : number = 300_000 ,
187+ bunkerParams : BunkerSignerParams = { } ,
188+ maxWaitOrAbort : number | AbortSignal = 300_000 ,
262189 ) : Promise < BunkerSigner > {
263- const signer = new BunkerSigner ( clientSecretKey , params )
264- const parsedURI = parseNostrConnectURI ( connectionURI )
190+ const signer = new BunkerSigner ( clientSecretKey , bunkerParams )
191+ const uri = new URL ( connectionURI )
265192 const clientPubkey = getPublicKey ( clientSecretKey )
266193
267194 return new Promise ( ( resolve , reject ) => {
268- const timer = setTimeout ( ( ) => {
269- sub . close ( )
270- reject ( new Error ( `Connection timed out after ${ maxWait / 1000 } seconds` ) )
271- } , maxWait )
272-
195+ let success = false
273196 const sub = signer . pool . subscribe (
274- parsedURI . params . relays ,
275- { kinds : [ NostrConnect ] , '#p' : [ clientPubkey ] } ,
197+ uri . searchParams . getAll ( 'relay' ) ,
198+ {
199+ kinds : [ NostrConnect ] ,
200+ '#p' : [ clientPubkey ] ,
201+ limit : 0 ,
202+ } ,
276203 {
277204 onevent : async ( event : NostrEvent ) => {
278205 try {
@@ -281,41 +208,48 @@ export class BunkerSigner implements Signer {
281208
282209 const response = JSON . parse ( decryptedContent )
283210
284- if ( response . result === parsedURI . params . secret ) {
285- clearTimeout ( timer )
211+ if ( response . result === uri . searchParams . get ( 'secret' ) ) {
286212 sub . close ( )
287213
288214 signer . bp = {
289215 pubkey : event . pubkey ,
290- relays : parsedURI . params . relays ,
291- secret : parsedURI . params . secret ,
216+ relays : uri . searchParams . getAll ( 'relay' ) ,
217+ secret : uri . searchParams . get ( ' secret' ) ,
292218 }
293219 signer . conversationKey = getConversationKey ( clientSecretKey , event . pubkey )
294- signer . setupSubscription ( params )
220+ signer . setupSubscription ( )
221+
222+ success = true
223+ await Promise . race ( [ new Promise ( resolve => setTimeout ( resolve , 1000 ) ) , signer . switchRelays ( ) ] )
295224 resolve ( signer )
296225 }
297226 } catch ( e ) {
298- console . warn ( 'Failed to process potential connection event' , e )
227+ console . warn ( 'failed to process potential connection event' , e )
299228 }
300229 } ,
301230 onclose : ( ) => {
302- clearTimeout ( timer )
303- reject ( new Error ( 'Subscription closed before connection was established.' ) )
231+ if ( ! success ) reject ( new Error ( 'subscription closed before connection was established.' ) )
304232 } ,
305- maxWait,
233+ maxWait : typeof maxWaitOrAbort === 'number' ? maxWaitOrAbort : undefined ,
234+ abort : typeof maxWaitOrAbort !== 'number' ? maxWaitOrAbort : undefined ,
306235 } ,
307236 )
308237 } )
309238 }
310239
311- private setupSubscription ( params : BunkerSignerParams ) {
240+ private setupSubscription ( ) {
312241 const listeners = this . listeners
313242 const waitingForAuth = this . waitingForAuth
314243 const convKey = this . conversationKey
315244
316245 this . subCloser = this . pool . subscribe (
317246 this . bp . relays ,
318- { kinds : [ NostrConnect ] , authors : [ this . bp . pubkey ] , '#p' : [ getPublicKey ( this . secretKey ) ] } ,
247+ {
248+ kinds : [ NostrConnect ] ,
249+ authors : [ this . bp . pubkey ] ,
250+ '#p' : [ getPublicKey ( this . secretKey ) ] ,
251+ limit : 0 ,
252+ } ,
319253 {
320254 onevent : async ( event : NostrEvent ) => {
321255 const o = JSON . parse ( decrypt ( event . content , convKey ) )
@@ -324,8 +258,8 @@ export class BunkerSigner implements Signer {
324258 if ( result === 'auth_url' && waitingForAuth [ id ] ) {
325259 delete waitingForAuth [ id ]
326260
327- if ( params . onauth ) {
328- params . onauth ( error )
261+ if ( this . params . onauth ) {
262+ this . params . onauth ( error )
329263 } else {
330264 console . warn (
331265 `nostr-tools/nip46: remote signer ${ this . bp . pubkey } tried to send an "auth_url"='${ error } ' but there was no onauth() callback configured.` ,
@@ -349,6 +283,27 @@ export class BunkerSigner implements Signer {
349283 this . isOpen = true
350284 }
351285
286+ async switchRelays ( ) : Promise < boolean > {
287+ try {
288+ const switchResp = await this . sendRequest ( 'switch_relays' , [ ] )
289+ let relays = JSON . parse ( switchResp ) as string [ ] | null
290+ if ( ! relays ) return false
291+ if ( JSON . stringify ( relays . sort ( ) ) === JSON . stringify ( this . bp . relays ) ) return false
292+
293+ this . bp . relays = relays
294+ let previousCloser = this . subCloser !
295+ setTimeout ( ( ) => {
296+ previousCloser . close ( )
297+ } , 5000 )
298+
299+ this . subCloser = undefined
300+ this . setupSubscription ( )
301+ return true
302+ } catch {
303+ return false
304+ }
305+ }
306+
352307 // closes the subscription -- this object can't be used anymore after this
353308 async close ( ) {
354309 this . isOpen = false
@@ -359,7 +314,7 @@ export class BunkerSigner implements Signer {
359314 return new Promise ( async ( resolve , reject ) => {
360315 try {
361316 if ( ! this . isOpen ) throw new Error ( 'this signer is not open anymore, create a new one' )
362- if ( ! this . subCloser ) this . setupSubscription ( this . params )
317+ if ( ! this . subCloser ) this . setupSubscription ( )
363318
364319 this . serial ++
365320 const id = `${ this . idPrefix } -${ this . serial } `
@@ -469,7 +424,7 @@ export async function createAccount(
469424 email ?: string ,
470425 localSecretKey : Uint8Array = generateSecretKey ( ) ,
471426) : Promise < BunkerSigner > {
472- if ( email && ! EMAIL_REGEX . test ( email ) ) throw new Error ( 'Invalid email' )
427+ if ( email && ! EMAIL_REGEX . test ( email ) ) throw new Error ( 'invalid email' )
473428
474429 let rpc = BunkerSigner . fromBunker ( localSecretKey , bunker . bunkerPointer , params )
475430
0 commit comments