applesauce-relay 0.0.0-next-20250430170741 → 0.0.0-next-20250430173639

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.
@@ -332,9 +332,59 @@ describe("notices$", () => {
332
332
  expect(relay.notices$.value).toEqual(["Important notice"]);
333
333
  });
334
334
  });
335
+ describe("notice$", () => {
336
+ it("should not trigger connection to relay", async () => {
337
+ subscribeSpyTo(relay.notice$);
338
+ await new Promise((resolve) => setTimeout(resolve, 10));
339
+ expect(relay.connected).toBe(false);
340
+ });
341
+ it("should emit NOTICE messages when they are received", async () => {
342
+ const spy = subscribeSpyTo(relay.notice$);
343
+ // Start connection
344
+ subscribeSpyTo(relay.req({ kinds: [1] }));
345
+ // Send multiple NOTICE messages
346
+ server.send(["NOTICE", "Notice 1"]);
347
+ server.send(["NOTICE", "Notice 2"]);
348
+ server.send(["NOTICE", "Notice 3"]);
349
+ // Verify the notices state contains all messages
350
+ expect(spy.getValues()).toEqual(["Notice 1", "Notice 2", "Notice 3"]);
351
+ });
352
+ it("should ignore non-NOTICE messages", async () => {
353
+ const spy = subscribeSpyTo(relay.notice$);
354
+ // Start connection
355
+ subscribeSpyTo(relay.req({ kinds: [1] }));
356
+ server.send(["NOTICE", "Important notice"]);
357
+ server.send(["OTHER", "other message"]);
358
+ // Verify only NOTICE messages are in the state
359
+ expect(spy.getValues()).toEqual(["Important notice"]);
360
+ });
361
+ });
362
+ describe("message$", () => {
363
+ it("should not trigger connection to relay", async () => {
364
+ subscribeSpyTo(relay.message$);
365
+ await new Promise((resolve) => setTimeout(resolve, 10));
366
+ expect(relay.connected).toBe(false);
367
+ });
368
+ it("should emit all messages when they are received", async () => {
369
+ const spy = subscribeSpyTo(relay.message$);
370
+ // Start connection
371
+ subscribeSpyTo(relay.req({ kinds: [1] }));
372
+ // Send multiple NOTICE messages
373
+ server.send(["NOTICE", "Notice 1"]);
374
+ server.send(["EVENT", "sub1", mockEvent]);
375
+ server.send(["EOSE", "sub1"]);
376
+ // Verify the notices state contains all messages
377
+ expect(spy.getValues()).toEqual([
378
+ ["NOTICE", "Notice 1"],
379
+ ["EVENT", "sub1", mockEvent],
380
+ ["EOSE", "sub1"],
381
+ ]);
382
+ });
383
+ });
335
384
  describe("challenge$", () => {
336
385
  it("should not trigger connection to relay", async () => {
337
386
  subscribeSpyTo(relay.challenge$);
387
+ await new Promise((resolve) => setTimeout(resolve, 10));
338
388
  expect(relay.connected).toBe(false);
339
389
  });
340
390
  it("should set challenge$ when AUTH message received", async () => {
package/dist/relay.d.ts CHANGED
@@ -30,21 +30,21 @@ export declare class Relay implements IRelay {
30
30
  notices$: BehaviorSubject<string[]>;
31
31
  /** The last connection error */
32
32
  error$: BehaviorSubject<Error | null>;
33
- /** An observable that emits the NIP-11 information document for the relay */
34
- information$: Observable<RelayInformation | null>;
35
- protected _nip11: RelayInformation | null;
36
- /** An observable that emits the limitations for the relay */
37
- limitations$: Observable<RelayInformation["limitation"] | null>;
38
33
  /**
39
- * An observable of all messages from the relay
40
- * @note Subscribing to this will cause the relay to connect
34
+ * A passive observable of all messages from the relay
35
+ * @note Subscribing to this will not connect to the relay
41
36
  */
42
37
  message$: Observable<any>;
43
38
  /**
44
- * An observable of NOTICE messages from the relay
45
- * @note Subscribing to this will cause the relay to connect
39
+ * A passive observable of NOTICE messages from the relay
40
+ * @note Subscribing to this will not connect to the relay
46
41
  */
47
42
  notice$: Observable<string>;
43
+ /** An observable that emits the NIP-11 information document for the relay */
44
+ information$: Observable<RelayInformation | null>;
45
+ protected _nip11: RelayInformation | null;
46
+ /** An observable that emits the limitations for the relay */
47
+ limitations$: Observable<RelayInformation["limitation"] | null>;
48
48
  get connected(): boolean;
49
49
  get challenge(): string | null;
50
50
  get notices(): string[];
package/dist/relay.js CHANGED
@@ -2,7 +2,7 @@ import { logger } from "applesauce-core";
2
2
  import { simpleTimeout } from "applesauce-core/observable";
3
3
  import { nanoid } from "nanoid";
4
4
  import { nip42 } from "nostr-tools";
5
- import { BehaviorSubject, catchError, combineLatest, defer, filter, from, ignoreElements, map, merge, mergeMap, NEVER, of, retry, scan, share, shareReplay, switchMap, take, tap, throwError, timeout, timer, } from "rxjs";
5
+ import { BehaviorSubject, catchError, combineLatest, defer, filter, from, ignoreElements, map, merge, mergeMap, NEVER, of, retry, scan, share, shareReplay, Subject, switchMap, take, tap, throwError, timeout, timer, } from "rxjs";
6
6
  import { webSocket } from "rxjs/webSocket";
7
7
  import { completeOnEose } from "./operators/complete-on-eose.js";
8
8
  import { markFromRelay } from "./operators/mark-from-relay.js";
@@ -29,21 +29,21 @@ export class Relay {
29
29
  notices$ = new BehaviorSubject([]);
30
30
  /** The last connection error */
31
31
  error$ = new BehaviorSubject(null);
32
- /** An observable that emits the NIP-11 information document for the relay */
33
- information$;
34
- _nip11 = null;
35
- /** An observable that emits the limitations for the relay */
36
- limitations$;
37
32
  /**
38
- * An observable of all messages from the relay
39
- * @note Subscribing to this will cause the relay to connect
33
+ * A passive observable of all messages from the relay
34
+ * @note Subscribing to this will not connect to the relay
40
35
  */
41
36
  message$;
42
37
  /**
43
- * An observable of NOTICE messages from the relay
44
- * @note Subscribing to this will cause the relay to connect
38
+ * A passive observable of NOTICE messages from the relay
39
+ * @note Subscribing to this will not connect to the relay
45
40
  */
46
41
  notice$;
42
+ /** An observable that emits the NIP-11 information document for the relay */
43
+ information$;
44
+ _nip11 = null;
45
+ /** An observable that emits the limitations for the relay */
46
+ limitations$;
47
47
  // sync state
48
48
  get connected() {
49
49
  return this.connected$.value;
@@ -116,7 +116,6 @@ export class Relay {
116
116
  },
117
117
  WebSocketCtor: opts?.WebSocket,
118
118
  });
119
- this.message$ = this.socket.asObservable();
120
119
  // Create an observable to fetch the NIP-11 information document
121
120
  this.information$ = defer(() => {
122
121
  this.log("Fetching NIP-11 information document");
@@ -132,19 +131,18 @@ export class Relay {
132
131
  // Create observables that track if auth is required for REQ or EVENT
133
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));
134
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));
135
- this.notice$ = this.message$.pipe(
134
+ // Update the notices state
135
+ const listenForNotice = this.socket.pipe(
136
136
  // listen for NOTICE messages
137
- filter((m) => m[0] === "NOTICE"),
137
+ filter((m) => Array.isArray(m) && m[0] === "NOTICE"),
138
138
  // pick the string out of the message
139
- map((m) => m[1]));
140
- // Update the notices state
141
- const notice = this.notice$.pipe(
139
+ map((m) => m[1]),
142
140
  // Track all notices
143
141
  scan((acc, notice) => [...acc, notice], []),
144
142
  // Update the notices state
145
143
  tap((notices) => this.notices$.next(notices)));
146
144
  // Update the challenge state
147
- const challenge = this.message$.pipe(
145
+ const ListenForChallenge = this.socket.pipe(
148
146
  // listen for AUTH messages
149
147
  filter((message) => message[0] === "AUTH"),
150
148
  // pick the challenge string out
@@ -154,12 +152,21 @@ export class Relay {
154
152
  this.log("Received AUTH challenge", challenge);
155
153
  this.challenge$.next(challenge);
156
154
  }));
155
+ const allMessagesSubject = new Subject();
156
+ const listenForAllMessages = this.socket.pipe(tap((message) => allMessagesSubject.next(message)));
157
+ // Create passive observables for messages and notices
158
+ this.message$ = allMessagesSubject.asObservable();
159
+ this.notice$ = this.message$.pipe(
160
+ // listen for NOTICE messages
161
+ filter((m) => Array.isArray(m) && m[0] === "NOTICE"),
162
+ // pick the string out of the message
163
+ map((m) => m[1]));
157
164
  // Merge all watchers
158
165
  this.watchTower = this.ready$.pipe(switchMap((ready) => {
159
166
  if (!ready)
160
167
  return NEVER;
161
168
  // Only start the watch tower if the relay is ready
162
- return merge(notice, challenge, this.information$).pipe(
169
+ return merge(listenForAllMessages, listenForNotice, ListenForChallenge, this.information$).pipe(
163
170
  // Never emit any values
164
171
  ignoreElements(),
165
172
  // Start the reconnect timer if the connection has an error
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-relay",
3
- "version": "0.0.0-next-20250430170741",
3
+ "version": "0.0.0-next-20250430173639",
4
4
  "description": "nostr relay communication framework built on rxjs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",