|
3 | 3 | import type { Event, EventTemplate, VerifiedEvent, Nostr, NostrEvent } from './core.ts' |
4 | 4 | import { matchFilters, type Filter } from './filter.ts' |
5 | 5 | import { getHex64, getSubscriptionId } from './fakejson.ts' |
6 | | -import { Queue, normalizeURL } from './utils.ts' |
| 6 | +import { normalizeURL } from './utils.ts' |
7 | 7 | import { makeAuthEvent } from './nip42.ts' |
8 | | -import { yieldThread } from './helpers.ts' |
9 | 8 |
|
10 | 9 | type RelayWebSocket = WebSocket & { |
11 | 10 | ping?(): void |
@@ -51,8 +50,6 @@ export class AbstractRelay { |
51 | 50 | private openCountRequests = new Map<string, CountResolver>() |
52 | 51 | private openEventPublishes = new Map<string, EventPublishResolver>() |
53 | 52 | private ws: RelayWebSocket | undefined |
54 | | - private incomingMessageQueue = new Queue<string>() |
55 | | - private queueRunning = false |
56 | 53 | private challenge: string | undefined |
57 | 54 | private authPromise: Promise<string> | undefined |
58 | 55 | private serial: number = 0 |
@@ -269,19 +266,110 @@ export class AbstractRelay { |
269 | 266 | } |
270 | 267 | } |
271 | 268 |
|
272 | | - private async runQueue() { |
273 | | - this.queueRunning = true |
274 | | - while (true) { |
275 | | - if (false === this.handleNext()) { |
276 | | - break |
| 269 | + public async send(message: string) { |
| 270 | + if (!this.connectionPromise) throw new SendingOnClosedConnection(message, this.url) |
| 271 | + |
| 272 | + this.connectionPromise.then(() => { |
| 273 | + this.ws?.send(message) |
| 274 | + }) |
| 275 | + } |
| 276 | + |
| 277 | + public async auth(signAuthEvent: (evt: EventTemplate) => Promise<VerifiedEvent>): Promise<string> { |
| 278 | + const challenge = this.challenge |
| 279 | + if (!challenge) throw new Error("can't perform auth, no challenge was received") |
| 280 | + if (this.authPromise) return this.authPromise |
| 281 | + |
| 282 | + this.authPromise = new Promise<string>(async (resolve, reject) => { |
| 283 | + try { |
| 284 | + let evt = await signAuthEvent(makeAuthEvent(this.url, challenge)) |
| 285 | + let timeout = setTimeout(() => { |
| 286 | + let ep = this.openEventPublishes.get(evt.id) as EventPublishResolver |
| 287 | + if (ep) { |
| 288 | + ep.reject(new Error('auth timed out')) |
| 289 | + this.openEventPublishes.delete(evt.id) |
| 290 | + } |
| 291 | + }, this.publishTimeout) |
| 292 | + this.openEventPublishes.set(evt.id, { resolve, reject, timeout }) |
| 293 | + this.send('["AUTH",' + JSON.stringify(evt) + ']') |
| 294 | + } catch (err) { |
| 295 | + console.warn('subscribe auth function failed:', err) |
277 | 296 | } |
278 | | - await yieldThread() |
| 297 | + }) |
| 298 | + return this.authPromise |
| 299 | + } |
| 300 | + |
| 301 | + public async publish(event: Event): Promise<string> { |
| 302 | + const ret = new Promise<string>((resolve, reject) => { |
| 303 | + const timeout = setTimeout(() => { |
| 304 | + const ep = this.openEventPublishes.get(event.id) as EventPublishResolver |
| 305 | + if (ep) { |
| 306 | + ep.reject(new Error('publish timed out')) |
| 307 | + this.openEventPublishes.delete(event.id) |
| 308 | + } |
| 309 | + }, this.publishTimeout) |
| 310 | + this.openEventPublishes.set(event.id, { resolve, reject, timeout }) |
| 311 | + }) |
| 312 | + this.send('["EVENT",' + JSON.stringify(event) + ']') |
| 313 | + return ret |
| 314 | + } |
| 315 | + |
| 316 | + public async count(filters: Filter[], params: { id?: string | null }): Promise<number> { |
| 317 | + this.serial++ |
| 318 | + const id = params?.id || 'count:' + this.serial |
| 319 | + const ret = new Promise<number>((resolve, reject) => { |
| 320 | + this.openCountRequests.set(id, { resolve, reject }) |
| 321 | + }) |
| 322 | + this.send('["COUNT","' + id + '",' + JSON.stringify(filters).substring(1)) |
| 323 | + return ret |
| 324 | + } |
| 325 | + |
| 326 | + public subscribe( |
| 327 | + filters: Filter[], |
| 328 | + params: Partial<SubscriptionParams> & { label?: string; id?: string }, |
| 329 | + ): Subscription { |
| 330 | + const sub = this.prepareSubscription(filters, params) |
| 331 | + sub.fire() |
| 332 | + |
| 333 | + if (params.abort) { |
| 334 | + params.abort.onabort = () => sub.close(String(params.abort!.reason || '<aborted>')) |
279 | 335 | } |
280 | | - this.queueRunning = false |
| 336 | + |
| 337 | + return sub |
| 338 | + } |
| 339 | + |
| 340 | + public prepareSubscription( |
| 341 | + filters: Filter[], |
| 342 | + params: Partial<SubscriptionParams> & { label?: string; id?: string }, |
| 343 | + ): Subscription { |
| 344 | + this.serial++ |
| 345 | + const id = params.id || (params.label ? params.label + ':' : 'sub:') + this.serial |
| 346 | + const subscription = new Subscription(this, id, filters, params) |
| 347 | + this.openSubs.set(id, subscription) |
| 348 | + return subscription |
281 | 349 | } |
282 | 350 |
|
283 | | - private handleNext(): undefined | false { |
284 | | - const json = this.incomingMessageQueue.dequeue() |
| 351 | + public close() { |
| 352 | + this.skipReconnection = true |
| 353 | + if (this.reconnectTimeoutHandle) { |
| 354 | + clearTimeout(this.reconnectTimeoutHandle) |
| 355 | + this.reconnectTimeoutHandle = undefined |
| 356 | + } |
| 357 | + if (this.pingIntervalHandle) { |
| 358 | + clearInterval(this.pingIntervalHandle) |
| 359 | + this.pingIntervalHandle = undefined |
| 360 | + } |
| 361 | + this.closeAllSubscriptions('relay connection closed by us') |
| 362 | + this._connected = false |
| 363 | + this.onclose?.() |
| 364 | + if (this.ws?.readyState === this._WebSocket.OPEN) { |
| 365 | + this.ws?.close() |
| 366 | + } |
| 367 | + } |
| 368 | + |
| 369 | + // this is the function assigned to this.ws.onmessage |
| 370 | + // it's exposed for testing and debugging purposes |
| 371 | + public _onmessage(ev: MessageEvent<any>) { |
| 372 | + const json = ev.data |
285 | 373 | if (!json) { |
286 | 374 | return false |
287 | 375 | } |
@@ -381,118 +469,11 @@ export class AbstractRelay { |
381 | 469 | } |
382 | 470 | } |
383 | 471 | } catch (err) { |
| 472 | + const [_, __, event] = JSON.parse(json) |
| 473 | + ;(window as any).printer.maybe(event.pubkey, ':: caught err', event, this.url, err) |
384 | 474 | return |
385 | 475 | } |
386 | 476 | } |
387 | | - |
388 | | - public async send(message: string) { |
389 | | - if (!this.connectionPromise) throw new SendingOnClosedConnection(message, this.url) |
390 | | - |
391 | | - this.connectionPromise.then(() => { |
392 | | - this.ws?.send(message) |
393 | | - }) |
394 | | - } |
395 | | - |
396 | | - public async auth(signAuthEvent: (evt: EventTemplate) => Promise<VerifiedEvent>): Promise<string> { |
397 | | - const challenge = this.challenge |
398 | | - if (!challenge) throw new Error("can't perform auth, no challenge was received") |
399 | | - if (this.authPromise) return this.authPromise |
400 | | - |
401 | | - this.authPromise = new Promise<string>(async (resolve, reject) => { |
402 | | - try { |
403 | | - let evt = await signAuthEvent(makeAuthEvent(this.url, challenge)) |
404 | | - let timeout = setTimeout(() => { |
405 | | - let ep = this.openEventPublishes.get(evt.id) as EventPublishResolver |
406 | | - if (ep) { |
407 | | - ep.reject(new Error('auth timed out')) |
408 | | - this.openEventPublishes.delete(evt.id) |
409 | | - } |
410 | | - }, this.publishTimeout) |
411 | | - this.openEventPublishes.set(evt.id, { resolve, reject, timeout }) |
412 | | - this.send('["AUTH",' + JSON.stringify(evt) + ']') |
413 | | - } catch (err) { |
414 | | - console.warn('subscribe auth function failed:', err) |
415 | | - } |
416 | | - }) |
417 | | - return this.authPromise |
418 | | - } |
419 | | - |
420 | | - public async publish(event: Event): Promise<string> { |
421 | | - const ret = new Promise<string>((resolve, reject) => { |
422 | | - const timeout = setTimeout(() => { |
423 | | - const ep = this.openEventPublishes.get(event.id) as EventPublishResolver |
424 | | - if (ep) { |
425 | | - ep.reject(new Error('publish timed out')) |
426 | | - this.openEventPublishes.delete(event.id) |
427 | | - } |
428 | | - }, this.publishTimeout) |
429 | | - this.openEventPublishes.set(event.id, { resolve, reject, timeout }) |
430 | | - }) |
431 | | - this.send('["EVENT",' + JSON.stringify(event) + ']') |
432 | | - return ret |
433 | | - } |
434 | | - |
435 | | - public async count(filters: Filter[], params: { id?: string | null }): Promise<number> { |
436 | | - this.serial++ |
437 | | - const id = params?.id || 'count:' + this.serial |
438 | | - const ret = new Promise<number>((resolve, reject) => { |
439 | | - this.openCountRequests.set(id, { resolve, reject }) |
440 | | - }) |
441 | | - this.send('["COUNT","' + id + '",' + JSON.stringify(filters).substring(1)) |
442 | | - return ret |
443 | | - } |
444 | | - |
445 | | - public subscribe( |
446 | | - filters: Filter[], |
447 | | - params: Partial<SubscriptionParams> & { label?: string; id?: string }, |
448 | | - ): Subscription { |
449 | | - const sub = this.prepareSubscription(filters, params) |
450 | | - sub.fire() |
451 | | - |
452 | | - if (params.abort) { |
453 | | - params.abort.onabort = () => sub.close(String(params.abort!.reason || '<aborted>')) |
454 | | - } |
455 | | - |
456 | | - return sub |
457 | | - } |
458 | | - |
459 | | - public prepareSubscription( |
460 | | - filters: Filter[], |
461 | | - params: Partial<SubscriptionParams> & { label?: string; id?: string }, |
462 | | - ): Subscription { |
463 | | - this.serial++ |
464 | | - const id = params.id || (params.label ? params.label + ':' : 'sub:') + this.serial |
465 | | - const subscription = new Subscription(this, id, filters, params) |
466 | | - this.openSubs.set(id, subscription) |
467 | | - return subscription |
468 | | - } |
469 | | - |
470 | | - public close() { |
471 | | - this.skipReconnection = true |
472 | | - if (this.reconnectTimeoutHandle) { |
473 | | - clearTimeout(this.reconnectTimeoutHandle) |
474 | | - this.reconnectTimeoutHandle = undefined |
475 | | - } |
476 | | - if (this.pingIntervalHandle) { |
477 | | - clearInterval(this.pingIntervalHandle) |
478 | | - this.pingIntervalHandle = undefined |
479 | | - } |
480 | | - this.closeAllSubscriptions('relay connection closed by us') |
481 | | - this._connected = false |
482 | | - this.onclose?.() |
483 | | - if (this.ws?.readyState === this._WebSocket.OPEN) { |
484 | | - this.ws?.close() |
485 | | - } |
486 | | - } |
487 | | - |
488 | | - // this is the function assigned to this.ws.onmessage |
489 | | - // it's exposed for testing and debugging purposes |
490 | | - public _onmessage(ev: MessageEvent<any>) { |
491 | | - this.incomingMessageQueue.enqueue(ev.data as string) |
492 | | - if (!this.queueRunning) { |
493 | | - this.runQueue() |
494 | | - } |
495 | | - } |
496 | 477 | } |
497 | 478 |
|
498 | 479 | export class Subscription { |
|
0 commit comments