applesauce-relay 2.0.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/relay.d.ts CHANGED
@@ -24,8 +24,10 @@ export declare class Relay implements IRelay {
24
24
  connected$: BehaviorSubject<boolean>;
25
25
  /** The authentication challenge string from the relay */
26
26
  challenge$: BehaviorSubject<string | null>;
27
- /** Whether the client is authenticated with the relay */
28
- authenticated$: BehaviorSubject<boolean>;
27
+ /** Boolean authentication state (will be false if auth failed) */
28
+ authenticated$: Observable<boolean>;
29
+ /** The response to the last AUTH message sent to the relay */
30
+ authenticationResponse$: BehaviorSubject<PublishResponse | null>;
29
31
  /** The notices from the relay */
30
32
  notices$: BehaviorSubject<string[]>;
31
33
  /** The last connection error */
@@ -49,6 +51,7 @@ export declare class Relay implements IRelay {
49
51
  get challenge(): string | null;
50
52
  get notices(): string[];
51
53
  get authenticated(): boolean;
54
+ get authenticationResponse(): PublishResponse | null;
52
55
  get information(): RelayInformation | null;
53
56
  /** If an EOSE message is not seen in this time, emit one locally */
54
57
  eoseTimeout: number;
@@ -58,8 +61,8 @@ export declare class Relay implements IRelay {
58
61
  keepAlive: number;
59
62
  protected receivedAuthRequiredForReq: BehaviorSubject<boolean>;
60
63
  protected receivedAuthRequiredForEvent: BehaviorSubject<boolean>;
61
- protected authRequiredForReq: Observable<boolean>;
62
- protected authRequiredForEvent: Observable<boolean>;
64
+ authRequiredForRead$: Observable<boolean>;
65
+ authRequiredForPublish$: Observable<boolean>;
63
66
  protected resetState(): void;
64
67
  /** An internal observable that is responsible for watching all messages and updating state, subscribing to it will trigger a connection to the relay */
65
68
  protected watchTower: Observable<never>;
package/dist/relay.js CHANGED
@@ -4,6 +4,7 @@ import { nanoid } from "nanoid";
4
4
  import { nip42 } from "nostr-tools";
5
5
  import { BehaviorSubject, catchError, combineLatest, defer, endWith, filter, finalize, from, ignoreElements, isObservable, map, merge, mergeMap, mergeWith, NEVER, of, retry, scan, share, shareReplay, Subject, switchMap, take, takeUntil, tap, throwError, timeout, timer, } from "rxjs";
6
6
  import { webSocket } from "rxjs/webSocket";
7
+ import { ensureHttpURL } from "applesauce-core/helpers";
7
8
  import { completeOnEose } from "./operators/complete-on-eose.js";
8
9
  import { markFromRelay } from "./operators/mark-from-relay.js";
9
10
  /** An error that is thrown when a REQ is closed from the relay side */
@@ -23,8 +24,10 @@ export class Relay {
23
24
  connected$ = new BehaviorSubject(false);
24
25
  /** The authentication challenge string from the relay */
25
26
  challenge$ = new BehaviorSubject(null);
26
- /** Whether the client is authenticated with the relay */
27
- authenticated$ = new BehaviorSubject(false);
27
+ /** Boolean authentication state (will be false if auth failed) */
28
+ authenticated$;
29
+ /** The response to the last AUTH message sent to the relay */
30
+ authenticationResponse$ = new BehaviorSubject(null);
28
31
  /** The notices from the relay */
29
32
  notices$ = new BehaviorSubject([]);
30
33
  /** The last connection error */
@@ -55,7 +58,10 @@ export class Relay {
55
58
  return this.notices$.value;
56
59
  }
57
60
  get authenticated() {
58
- return this.authenticated$.value;
61
+ return this.authenticationResponse?.ok === true;
62
+ }
63
+ get authenticationResponse() {
64
+ return this.authenticationResponse$.value;
59
65
  }
60
66
  get information() {
61
67
  return this._nip11;
@@ -66,18 +72,18 @@ export class Relay {
66
72
  eventTimeout = 10_000;
67
73
  /** How long to keep the connection alive after nothing is subscribed */
68
74
  keepAlive = 30_000;
69
- // subjects that track if an "auth-required" message has been received for REQ or EVENT
75
+ // Subjects that track if an "auth-required" message has been received for REQ or EVENT
70
76
  receivedAuthRequiredForReq = new BehaviorSubject(false);
71
77
  receivedAuthRequiredForEvent = new BehaviorSubject(false);
72
78
  // Computed observables that track if auth is required for REQ or EVENT
73
- authRequiredForReq;
74
- authRequiredForEvent;
79
+ authRequiredForRead$;
80
+ authRequiredForPublish$;
75
81
  resetState() {
76
82
  // NOTE: only update the values if they need to be changed, otherwise this will cause an infinite loop
77
83
  if (this.challenge$.value !== null)
78
84
  this.challenge$.next(null);
79
- if (this.authenticated$.value)
80
- this.authenticated$.next(false);
85
+ if (this.authenticationResponse$.value)
86
+ this.authenticationResponse$.next(null);
81
87
  if (this.notices$.value.length > 0)
82
88
  this.notices$.next([]);
83
89
  if (this.receivedAuthRequiredForReq.value)
@@ -90,6 +96,8 @@ export class Relay {
90
96
  constructor(url, opts) {
91
97
  this.url = url;
92
98
  this.log = this.log.extend(url);
99
+ // Create an observable that tracks boolean authentication state
100
+ this.authenticated$ = this.authenticationResponse$.pipe(map((response) => response?.ok === true));
93
101
  /** Use the static method to create a new reconnect method for this relay */
94
102
  this.reconnectTimer = Relay.createReconnectTimer(url);
95
103
  this.socket = webSocket({
@@ -129,8 +137,8 @@ export class Relay {
129
137
  tap((info) => (this._nip11 = info)));
130
138
  this.limitations$ = this.information$.pipe(map((info) => info?.limitation));
131
139
  // Create observables that track if auth is required for REQ or EVENT
132
- this.authRequiredForReq = combineLatest([this.receivedAuthRequiredForReq, this.limitations$]).pipe(map(([received, limitations]) => received || limitations?.auth_required === true), tap((required) => required && this.log("Auth required for REQ")), shareReplay(1));
133
- this.authRequiredForEvent = combineLatest([this.receivedAuthRequiredForEvent, this.limitations$]).pipe(map(([received, limitations]) => received || limitations?.auth_required === true), tap((required) => required && this.log("Auth required for EVENT")), shareReplay(1));
140
+ this.authRequiredForRead$ = this.receivedAuthRequiredForReq.pipe(tap((required) => required && this.log("Auth required for REQ")), shareReplay(1));
141
+ this.authRequiredForPublish$ = this.receivedAuthRequiredForEvent.pipe(tap((required) => required && this.log("Auth required for EVENT")), shareReplay(1));
134
142
  // Update the notices state
135
143
  const listenForNotice = this.socket.pipe(
136
144
  // listen for NOTICE messages
@@ -275,7 +283,7 @@ export class Relay {
275
283
  // Only create one upstream subscription
276
284
  share());
277
285
  // Wait for auth if required and make sure to start the watch tower
278
- return this.waitForReady(this.waitForAuth(this.authRequiredForReq, observable));
286
+ return this.waitForReady(this.waitForAuth(this.authRequiredForRead$, observable));
279
287
  }
280
288
  /** Send an EVENT or AUTH message and return an observable of PublishResponse that completes or errors */
281
289
  event(event, verb = "EVENT") {
@@ -308,13 +316,13 @@ export class Relay {
308
316
  if (verb === "AUTH")
309
317
  return this.waitForReady(observable);
310
318
  else
311
- return this.waitForReady(this.waitForAuth(this.authRequiredForEvent, observable));
319
+ return this.waitForReady(this.waitForAuth(this.authRequiredForPublish$, observable));
312
320
  }
313
321
  /** send and AUTH message */
314
322
  auth(event) {
315
323
  return this.event(event, "AUTH").pipe(
316
324
  // update authenticated
317
- tap((result) => this.authenticated$.next(result.ok)));
325
+ tap((result) => this.authenticationResponse$.next(result)));
318
326
  }
319
327
  /** Authenticate with the relay using a signer */
320
328
  authenticate(signer) {
@@ -351,7 +359,7 @@ export class Relay {
351
359
  }
352
360
  /** Static method to fetch the NIP-11 information document for a relay */
353
361
  static fetchInformationDocument(url) {
354
- return from(fetch(url, { headers: { Accept: "application/nostr+json" } }).then((res) => res.json())).pipe(
362
+ return from(fetch(ensureHttpURL(url), { headers: { Accept: "application/nostr+json" } }).then((res) => res.json())).pipe(
355
363
  // if the fetch fails, return null
356
364
  catchError(() => of(null)),
357
365
  // timeout after 10s
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-relay",
3
- "version": "2.0.0",
3
+ "version": "2.3.0",
4
4
  "description": "nostr relay communication framework built on rxjs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -54,7 +54,7 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@noble/hashes": "^1.7.1",
57
- "applesauce-core": "^2.0.0",
57
+ "applesauce-core": "^2.3.0",
58
58
  "nanoid": "^5.0.9",
59
59
  "nostr-tools": "^2.13",
60
60
  "rxjs": "^7.8.1"