applesauce-relay 0.0.0-next-20250703183032 → 0.0.0-next-20250718170433

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/README.md CHANGED
@@ -56,9 +56,8 @@ const event = {
56
56
  // ... other required fields
57
57
  };
58
58
 
59
- relay.event(event).subscribe((response) => {
60
- console.log(`Published:`, response.ok);
61
- });
59
+ const response = await relay.publish(event);
60
+ console.log(`Published:`, response.ok);
62
61
  ```
63
62
 
64
63
  ### Relay Pool
@@ -85,7 +84,8 @@ pool
85
84
  });
86
85
 
87
86
  // Publish to multiple relays
88
- pool.event(relays, event).subscribe((response) => {
87
+ const responses = await pool.publish(relays, event);
88
+ responses.forEach((response) => {
89
89
  console.log(`Published to ${response.from}:`, response.ok);
90
90
  });
91
91
  ```
@@ -112,7 +112,8 @@ group
112
112
  });
113
113
 
114
114
  // Publish to all relays in group
115
- group.event(event).subscribe((response) => {
115
+ const responses = await group.publish(event);
116
+ responses.forEach((response) => {
116
117
  console.log(`Published to ${response.from}:`, response.ok);
117
118
  });
118
119
  ```
@@ -144,7 +144,7 @@ describe("req", () => {
144
144
  // Verify the second subscription received the event and EOSE
145
145
  expect(secondSub.getValues()).toEqual([expect.objectContaining(mockEvent), "EOSE"]);
146
146
  });
147
- it("should open connection and wait for authentication if relay info document has limitations.auth_required = true", async () => {
147
+ it("should wait for authentication if relay info document has limitations.auth_required = true", async () => {
148
148
  // Mock the fetchInformationDocument method to return a document with auth_required = true
149
149
  vi.spyOn(Relay, "fetchInformationDocument").mockImplementation(() => of({
150
150
  name: "Auth Required Relay",
@@ -162,20 +162,10 @@ describe("req", () => {
162
162
  const sub = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
163
163
  // Wait 10ms to ensure the information document is fetched
164
164
  await new Promise((resolve) => setTimeout(resolve, 10));
165
- // Wait for connection
166
- await server.connected;
167
165
  // Verify no REQ message was sent yet (waiting for auth)
168
166
  expect(server).not.toHaveReceivedMessages(["REQ", "sub1", { kinds: [1] }]);
169
- // Send AUTH challenge
170
- server.send(["AUTH", "challenge"]);
171
- // Send auth response
172
- subscribeSpyTo(relay.auth(mockEvent));
173
- // Verify the auth event was sent
174
- await expect(server.nextMessage).resolves.toEqual(["AUTH", mockEvent]);
175
- // Accept auth
176
- server.send(["OK", mockEvent.id, true, ""]);
177
167
  // Simulate successful authentication
178
- expect(relay.authenticated).toBe(true);
168
+ relay.authenticated$.next(true);
179
169
  // Now the REQ should be sent
180
170
  await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
181
171
  // Send EVENT and EOSE to complete the subscription
package/dist/group.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type NostrEvent } from "nostr-tools";
2
2
  import { Observable } from "rxjs";
3
- import { IGroup, IRelay, PublishResponse, SubscriptionResponse, PublishOptions, RequestOptions, SubscriptionOptions, FilterInput } from "./types.js";
3
+ import { FilterInput, IGroup, IRelay, PublishOptions, PublishResponse, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
4
4
  export declare class RelayGroup implements IGroup {
5
5
  relays: IRelay[];
6
6
  constructor(relays: IRelay[]);
@@ -11,7 +11,7 @@ export declare class RelayGroup implements IGroup {
11
11
  /** Send an event to all relays */
12
12
  event(event: NostrEvent): Observable<PublishResponse>;
13
13
  /** Publish an event to all relays with retries ( default 3 retries ) */
14
- publish(event: NostrEvent, opts?: PublishOptions): Observable<PublishResponse>;
14
+ publish(event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse[]>;
15
15
  /** Request events from all relays with retries ( default 3 retries ) */
16
16
  request(filters: FilterInput, opts?: RequestOptions): Observable<NostrEvent>;
17
17
  /** Open a subscription to all relays with retries ( default 3 retries ) */
package/dist/group.js CHANGED
@@ -35,9 +35,9 @@ export class RelayGroup {
35
35
  }
36
36
  /** Publish an event to all relays with retries ( default 3 retries ) */
37
37
  publish(event, opts) {
38
- return merge(...this.relays.map((relay) => relay.publish(event, opts).pipe(
38
+ return Promise.all(this.relays.map((relay) => relay.publish(event, opts).catch(
39
39
  // Catch error and return as PublishResponse
40
- catchError((err) => of({ ok: false, from: relay.url, message: err?.message || "Unknown error" })))));
40
+ (err) => ({ ok: false, from: relay.url, message: err?.message || "Unknown error" }))));
41
41
  }
42
42
  /** Request events from all relays with retries ( default 3 retries ) */
43
43
  request(filters, opts) {
package/dist/pool.d.ts CHANGED
@@ -22,7 +22,7 @@ export declare class RelayPool implements IPool {
22
22
  /** Send an EVENT message to multiple relays */
23
23
  event(relays: string[], event: NostrEvent): Observable<PublishResponse>;
24
24
  /** Publish an event to multiple relays */
25
- publish(relays: string[], event: NostrEvent, opts?: PublishOptions): Observable<PublishResponse>;
25
+ publish(relays: string[], event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse[]>;
26
26
  /** Request events from multiple relays */
27
27
  request(relays: string[], filters: FilterInput, opts?: RequestOptions): Observable<NostrEvent>;
28
28
  /** Open a subscription to multiple relays */
package/dist/relay.d.ts CHANGED
@@ -75,21 +75,21 @@ export declare class Relay implements IRelay {
75
75
  protected waitForReady<T extends unknown = unknown>(observable: Observable<T>): Observable<T>;
76
76
  multiplex<T>(open: () => any, close: () => any, filter: (message: any) => boolean): Observable<T>;
77
77
  /** Send a message to the relay */
78
- next(message: any): void;
78
+ send(message: any): void;
79
79
  /** Create a REQ observable that emits events or "EOSE" or errors */
80
80
  req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
81
81
  /** Send an EVENT or AUTH message and return an observable of PublishResponse that completes or errors */
82
82
  event(event: NostrEvent, verb?: "EVENT" | "AUTH"): Observable<PublishResponse>;
83
83
  /** send and AUTH message */
84
- auth(event: NostrEvent): Observable<PublishResponse>;
84
+ auth(event: NostrEvent): Promise<PublishResponse>;
85
85
  /** Authenticate with the relay using a signer */
86
- authenticate(signer: AuthSigner): Observable<PublishResponse>;
86
+ authenticate(signer: AuthSigner): Promise<PublishResponse>;
87
87
  /** Creates a REQ that retries when relay errors ( default 3 retries ) */
88
88
  subscription(filters: Filter | Filter[], opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
89
89
  /** Makes a single request that retires on errors and completes on EOSE */
90
90
  request(filters: Filter | Filter[], opts?: RequestOptions): Observable<NostrEvent>;
91
91
  /** Publishes an event to the relay and retries when relay errors or responds with auth-required ( default 3 retries ) */
92
- publish(event: NostrEvent, opts?: PublishOptions): Observable<PublishResponse>;
92
+ publish(event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse>;
93
93
  /** Static method to fetch the NIP-11 information document for a relay */
94
94
  static fetchInformationDocument(url: string): Observable<RelayInformation | null>;
95
95
  /** Static method to create a reconnection method for each relay */
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, 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";
5
+ import { BehaviorSubject, catchError, combineLatest, defer, endWith, filter, finalize, from, ignoreElements, isObservable, lastValueFrom, 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
7
  import { ensureHttpURL } from "applesauce-core/helpers";
8
8
  import { completeOnEose } from "./operators/complete-on-eose.js";
@@ -230,7 +230,7 @@ export class Relay {
230
230
  return this.socket.multiplex(open, close, filter);
231
231
  }
232
232
  /** Send a message to the relay */
233
- next(message) {
233
+ send(message) {
234
234
  this.socket.next(message);
235
235
  }
236
236
  /** Create a REQ observable that emits events or "EOSE" or errors */
@@ -320,9 +320,9 @@ export class Relay {
320
320
  }
321
321
  /** send and AUTH message */
322
322
  auth(event) {
323
- return this.event(event, "AUTH").pipe(
323
+ return lastValueFrom(this.event(event, "AUTH").pipe(
324
324
  // update authenticated
325
- tap((result) => this.authenticationResponse$.next(result)));
325
+ tap((result) => this.authenticationResponse$.next(result))));
326
326
  }
327
327
  /** Authenticate with the relay using a signer */
328
328
  authenticate(signer) {
@@ -330,7 +330,7 @@ export class Relay {
330
330
  throw new Error("Have not received authentication challenge");
331
331
  const p = signer.signEvent(nip42.makeAuthEvent(this.url, this.challenge));
332
332
  const start = p instanceof Promise ? from(p) : of(p);
333
- return start.pipe(switchMap((event) => this.auth(event)));
333
+ return lastValueFrom(start.pipe(switchMap((event) => this.auth(event))));
334
334
  }
335
335
  /** Creates a REQ that retries when relay errors ( default 3 retries ) */
336
336
  subscription(filters, opts) {
@@ -348,14 +348,14 @@ export class Relay {
348
348
  }
349
349
  /** Publishes an event to the relay and retries when relay errors or responds with auth-required ( default 3 retries ) */
350
350
  publish(event, opts) {
351
- return this.event(event).pipe(mergeMap((result) => {
351
+ return lastValueFrom(this.event(event).pipe(mergeMap((result) => {
352
352
  // If the relay responds with auth-required, throw an error for the retry operator to handle
353
353
  if (result.ok === false && result.message?.startsWith("auth-required:"))
354
354
  return throwError(() => new Error(result.message));
355
355
  return of(result);
356
356
  }),
357
357
  // Retry the publish until it succeeds or the number of retries is reached
358
- retry(opts?.retries ?? 3));
358
+ retry(opts?.retries ?? 3)));
359
359
  }
360
360
  /** Static method to fetch the NIP-11 information document for a relay */
361
361
  static fetchInformationDocument(url) {
package/dist/types.d.ts CHANGED
@@ -8,12 +8,6 @@ export type PublishResponse = {
8
8
  from: string;
9
9
  };
10
10
  export type MultiplexWebSocket<T = any> = Pick<WebSocketSubject<T>, "multiplex">;
11
- export interface IRelayState {
12
- connected$: Observable<boolean>;
13
- challenge$: Observable<string | null>;
14
- authenticated$: Observable<boolean>;
15
- notices$: Observable<string[]>;
16
- }
17
11
  export type PublishOptions = {
18
12
  retries?: number;
19
13
  };
@@ -30,29 +24,30 @@ export type AuthSigner = {
30
24
  };
31
25
  /** The type of input the REQ method accepts */
32
26
  export type FilterInput = Filter | Filter[] | Observable<Filter | Filter[]>;
33
- export interface Nip01Actions {
34
- /** Send an EVENT message */
35
- event(event: NostrEvent): Observable<PublishResponse>;
36
- /** Send a REQ message */
37
- req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
38
- }
39
- export interface IRelay extends MultiplexWebSocket, Nip01Actions, IRelayState {
27
+ export interface IRelay extends MultiplexWebSocket {
40
28
  url: string;
41
29
  message$: Observable<any>;
42
30
  notice$: Observable<string>;
31
+ connected$: Observable<boolean>;
32
+ challenge$: Observable<string | null>;
33
+ authenticated$: Observable<boolean>;
34
+ notices$: Observable<string[]>;
43
35
  readonly connected: boolean;
44
36
  readonly authenticated: boolean;
45
37
  readonly challenge: string | null;
46
38
  readonly notices: string[];
39
+ /** Send a REQ message */
40
+ req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
41
+ /** Send an EVENT message */
42
+ event(event: NostrEvent): Observable<PublishResponse>;
47
43
  /** Send an AUTH message */
48
- auth(event: NostrEvent): Observable<{
49
- ok: boolean;
50
- message?: string;
51
- }>;
44
+ auth(event: NostrEvent): Promise<PublishResponse>;
45
+ /** Authenticate with the relay using a signer */
46
+ authenticate(signer: AuthSigner): Promise<PublishResponse>;
52
47
  /** Send an EVENT message with retries */
53
48
  publish(event: NostrEvent, opts?: {
54
49
  retries?: number;
55
- }): Observable<PublishResponse>;
50
+ }): Promise<PublishResponse>;
56
51
  /** Send a REQ message with retries */
57
52
  request(filters: FilterInput, opts?: {
58
53
  id?: string;
@@ -64,11 +59,15 @@ export interface IRelay extends MultiplexWebSocket, Nip01Actions, IRelayState {
64
59
  retries?: number;
65
60
  }): Observable<SubscriptionResponse>;
66
61
  }
67
- export interface IGroup extends Nip01Actions {
62
+ export interface IGroup {
63
+ /** Send a REQ message */
64
+ req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
65
+ /** Send an EVENT message */
66
+ event(event: NostrEvent): Observable<PublishResponse>;
68
67
  /** Send an EVENT message with retries */
69
68
  publish(event: NostrEvent, opts?: {
70
69
  retries?: number;
71
- }): Observable<PublishResponse>;
70
+ }): Promise<PublishResponse[]>;
72
71
  /** Send a REQ message with retries */
73
72
  request(filters: FilterInput, opts?: {
74
73
  id?: string;
@@ -81,18 +80,18 @@ export interface IGroup extends Nip01Actions {
81
80
  }): Observable<SubscriptionResponse>;
82
81
  }
83
82
  export interface IPool {
84
- /** Send an EVENT message */
85
- event(relays: string[], event: NostrEvent): Observable<PublishResponse>;
86
- /** Send a REQ message */
87
- req(relays: string[], filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
88
83
  /** Get or create a relay */
89
84
  relay(url: string): IRelay;
90
85
  /** Create a relay group */
91
86
  group(relays: string[]): IGroup;
87
+ /** Send a REQ message */
88
+ req(relays: string[], filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
89
+ /** Send an EVENT message */
90
+ event(relays: string[], event: NostrEvent): Observable<PublishResponse>;
92
91
  /** Send an EVENT message to relays with retries */
93
92
  publish(relays: string[], event: NostrEvent, opts?: {
94
93
  retries?: number;
95
- }): Observable<PublishResponse>;
94
+ }): Promise<PublishResponse[]>;
96
95
  /** Send a REQ message to relays with retries */
97
96
  request(relays: string[], filters: FilterInput, opts?: {
98
97
  id?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-relay",
3
- "version": "0.0.0-next-20250703183032",
3
+ "version": "0.0.0-next-20250718170433",
4
4
  "description": "nostr relay communication framework built on rxjs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -54,14 +54,14 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@noble/hashes": "^1.7.1",
57
- "applesauce-core": "0.0.0-next-20250703183032",
57
+ "applesauce-core": "^2.3.0",
58
58
  "nanoid": "^5.0.9",
59
59
  "nostr-tools": "^2.13",
60
60
  "rxjs": "^7.8.1"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@hirez_io/observer-spy": "^2.2.0",
64
- "applesauce-signers": "^2.0.0",
64
+ "applesauce-signers": "0.0.0-next-20250718170433",
65
65
  "@vitest/expect": "^3.1.1",
66
66
  "typescript": "^5.7.3",
67
67
  "vitest": "^3.2.3",