applesauce-relay 1.0.0 → 1.1.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/__tests__/relay.test.js +78 -4
- package/dist/group.d.ts +1 -1
- package/dist/group.js +2 -2
- package/dist/pool.d.ts +9 -4
- package/dist/pool.js +19 -4
- package/dist/relay.d.ts +12 -4
- package/dist/relay.js +47 -22
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
|
@@ -47,17 +47,33 @@ describe("req", () => {
|
|
|
47
47
|
await firstValueFrom(relay.connected$.pipe(filter(Boolean)));
|
|
48
48
|
expect(relay.connected).toBe(true);
|
|
49
49
|
});
|
|
50
|
-
it("should send
|
|
50
|
+
it("should send expected messages to relay", async () => {
|
|
51
|
+
subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
|
|
52
|
+
// Wait for all message to be sent
|
|
53
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
54
|
+
expect(server.messages).toEqual([["REQ", "sub1", { kinds: [1] }]]);
|
|
55
|
+
});
|
|
56
|
+
it("should not close the REQ when EOSE is received", async () => {
|
|
51
57
|
// Create subscription that completes after first EOSE
|
|
52
|
-
const sub = relay.req([{ kinds: [1] }], "sub1")
|
|
58
|
+
const sub = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
|
|
53
59
|
// Verify REQ was sent
|
|
54
|
-
expect(
|
|
60
|
+
await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
|
|
55
61
|
// Send EOSE to complete subscription
|
|
62
|
+
server.send(["EVENT", "sub1", mockEvent]);
|
|
56
63
|
server.send(["EOSE", "sub1"]);
|
|
64
|
+
// Verify the subscription did not complete
|
|
65
|
+
expect(sub.receivedComplete()).toBe(false);
|
|
66
|
+
expect(sub.getValues()).toEqual([expect.objectContaining(mockEvent), "EOSE"]);
|
|
67
|
+
});
|
|
68
|
+
it("should send CLOSE when unsubscribed", async () => {
|
|
69
|
+
// Create subscription that completes after first EOSE
|
|
70
|
+
const sub = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
|
|
71
|
+
// Verify REQ was sent
|
|
72
|
+
await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
|
|
57
73
|
// Complete the subscription
|
|
58
74
|
sub.unsubscribe();
|
|
59
75
|
// Verify CLOSE was sent
|
|
60
|
-
expect(
|
|
76
|
+
await expect(server).toReceiveMessage(["CLOSE", "sub1"]);
|
|
61
77
|
});
|
|
62
78
|
it("should emit nostr event and EOSE", async () => {
|
|
63
79
|
const spy = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
|
|
@@ -99,6 +115,14 @@ describe("req", () => {
|
|
|
99
115
|
// Verify the subscription completed
|
|
100
116
|
expect(spy.receivedError()).toBe(true);
|
|
101
117
|
});
|
|
118
|
+
it("should not send multiple REQ messages for multiple subscriptions", async () => {
|
|
119
|
+
const sub = relay.req([{ kinds: [1] }], "sub1");
|
|
120
|
+
sub.subscribe();
|
|
121
|
+
sub.subscribe();
|
|
122
|
+
// Wait for all messages to be sent
|
|
123
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
124
|
+
expect(server.messages).toEqual([["REQ", "sub1", { kinds: [1] }]]);
|
|
125
|
+
});
|
|
102
126
|
it("should wait for authentication if relay responds with auth-required", async () => {
|
|
103
127
|
// First subscription to trigger auth-required
|
|
104
128
|
const firstSub = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"), { expectErrors: true });
|
|
@@ -332,9 +356,59 @@ describe("notices$", () => {
|
|
|
332
356
|
expect(relay.notices$.value).toEqual(["Important notice"]);
|
|
333
357
|
});
|
|
334
358
|
});
|
|
359
|
+
describe("notice$", () => {
|
|
360
|
+
it("should not trigger connection to relay", async () => {
|
|
361
|
+
subscribeSpyTo(relay.notice$);
|
|
362
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
363
|
+
expect(relay.connected).toBe(false);
|
|
364
|
+
});
|
|
365
|
+
it("should emit NOTICE messages when they are received", async () => {
|
|
366
|
+
const spy = subscribeSpyTo(relay.notice$);
|
|
367
|
+
// Start connection
|
|
368
|
+
subscribeSpyTo(relay.req({ kinds: [1] }));
|
|
369
|
+
// Send multiple NOTICE messages
|
|
370
|
+
server.send(["NOTICE", "Notice 1"]);
|
|
371
|
+
server.send(["NOTICE", "Notice 2"]);
|
|
372
|
+
server.send(["NOTICE", "Notice 3"]);
|
|
373
|
+
// Verify the notices state contains all messages
|
|
374
|
+
expect(spy.getValues()).toEqual(["Notice 1", "Notice 2", "Notice 3"]);
|
|
375
|
+
});
|
|
376
|
+
it("should ignore non-NOTICE messages", async () => {
|
|
377
|
+
const spy = subscribeSpyTo(relay.notice$);
|
|
378
|
+
// Start connection
|
|
379
|
+
subscribeSpyTo(relay.req({ kinds: [1] }));
|
|
380
|
+
server.send(["NOTICE", "Important notice"]);
|
|
381
|
+
server.send(["OTHER", "other message"]);
|
|
382
|
+
// Verify only NOTICE messages are in the state
|
|
383
|
+
expect(spy.getValues()).toEqual(["Important notice"]);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
describe("message$", () => {
|
|
387
|
+
it("should not trigger connection to relay", async () => {
|
|
388
|
+
subscribeSpyTo(relay.message$);
|
|
389
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
390
|
+
expect(relay.connected).toBe(false);
|
|
391
|
+
});
|
|
392
|
+
it("should emit all messages when they are received", async () => {
|
|
393
|
+
const spy = subscribeSpyTo(relay.message$);
|
|
394
|
+
// Start connection
|
|
395
|
+
subscribeSpyTo(relay.req({ kinds: [1] }));
|
|
396
|
+
// Send multiple NOTICE messages
|
|
397
|
+
server.send(["NOTICE", "Notice 1"]);
|
|
398
|
+
server.send(["EVENT", "sub1", mockEvent]);
|
|
399
|
+
server.send(["EOSE", "sub1"]);
|
|
400
|
+
// Verify the notices state contains all messages
|
|
401
|
+
expect(spy.getValues()).toEqual([
|
|
402
|
+
["NOTICE", "Notice 1"],
|
|
403
|
+
["EVENT", "sub1", mockEvent],
|
|
404
|
+
["EOSE", "sub1"],
|
|
405
|
+
]);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
335
408
|
describe("challenge$", () => {
|
|
336
409
|
it("should not trigger connection to relay", async () => {
|
|
337
410
|
subscribeSpyTo(relay.challenge$);
|
|
411
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
338
412
|
expect(relay.connected).toBe(false);
|
|
339
413
|
});
|
|
340
414
|
it("should set challenge$ when AUTH message received", async () => {
|
package/dist/group.d.ts
CHANGED
|
@@ -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): Observable<PublishResponse>;
|
|
15
15
|
/** Request events from all relays with retries ( default 3 retries ) */
|
|
16
16
|
request(filters: Filter | Filter[], opts?: RequestOptions): Observable<NostrEvent>;
|
|
17
17
|
/** Open a subscription to all relays with retries ( default 3 retries ) */
|
package/dist/group.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { nanoid } from "nanoid";
|
|
2
|
-
import { catchError, EMPTY, endWith, ignoreElements, merge, of
|
|
2
|
+
import { catchError, EMPTY, endWith, ignoreElements, merge, of } from "rxjs";
|
|
3
3
|
import { completeOnEose } from "./operators/complete-on-eose.js";
|
|
4
4
|
import { onlyEvents } from "./operators/only-events.js";
|
|
5
5
|
export class RelayGroup {
|
|
@@ -37,7 +37,7 @@ export class RelayGroup {
|
|
|
37
37
|
publish(event, opts) {
|
|
38
38
|
return merge(...this.relays.map((relay) => relay.publish(event, opts).pipe(
|
|
39
39
|
// Catch error and return as PublishResponse
|
|
40
|
-
catchError((err) => of({ ok: false, from: relay.url, message: err?.message || "Unknown error" })))))
|
|
40
|
+
catchError((err) => of({ 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
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { NostrEvent, type Filter } from "nostr-tools";
|
|
2
|
-
import { Observable } from "rxjs";
|
|
2
|
+
import { BehaviorSubject, Observable } from "rxjs";
|
|
3
3
|
import { RelayGroup } from "./group.js";
|
|
4
4
|
import { Relay, RelayOptions } from "./relay.js";
|
|
5
5
|
import { IPool, PublishResponse, PublishOptions, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
|
|
6
6
|
export declare class RelayPool implements IPool {
|
|
7
7
|
options?: RelayOptions | undefined;
|
|
8
|
-
|
|
9
|
-
groups: Map<string, RelayGroup>;
|
|
8
|
+
groups$: BehaviorSubject<Map<string, RelayGroup>>;
|
|
9
|
+
get groups(): Map<string, RelayGroup>;
|
|
10
|
+
relays$: BehaviorSubject<Map<string, Relay>>;
|
|
11
|
+
get relays(): Map<string, Relay>;
|
|
12
|
+
/** An array of relays to never connect to */
|
|
13
|
+
blacklist: Set<string>;
|
|
10
14
|
constructor(options?: RelayOptions | undefined);
|
|
15
|
+
protected filterBlacklist(urls: string[]): string[];
|
|
11
16
|
/** Get or create a new relay connection */
|
|
12
17
|
relay(url: string): Relay;
|
|
13
18
|
/** Create a group of relays */
|
|
@@ -17,7 +22,7 @@ export declare class RelayPool implements IPool {
|
|
|
17
22
|
/** Send an EVENT message to multiple relays */
|
|
18
23
|
event(relays: string[], event: NostrEvent): Observable<PublishResponse>;
|
|
19
24
|
/** Publish an event to multiple relays */
|
|
20
|
-
publish(relays: string[], event: NostrEvent, opts?: PublishOptions): Observable<PublishResponse
|
|
25
|
+
publish(relays: string[], event: NostrEvent, opts?: PublishOptions): Observable<PublishResponse>;
|
|
21
26
|
/** Request events from multiple relays */
|
|
22
27
|
request(relays: string[], filters: Filter | Filter[], opts?: RequestOptions): Observable<NostrEvent>;
|
|
23
28
|
/** Open a subscription to multiple relays */
|
package/dist/pool.js
CHANGED
|
@@ -1,31 +1,46 @@
|
|
|
1
|
+
import { BehaviorSubject } from "rxjs";
|
|
1
2
|
import { RelayGroup } from "./group.js";
|
|
2
3
|
import { Relay } from "./relay.js";
|
|
3
4
|
export class RelayPool {
|
|
4
5
|
options;
|
|
5
|
-
|
|
6
|
-
groups
|
|
6
|
+
groups$ = new BehaviorSubject(new Map());
|
|
7
|
+
get groups() {
|
|
8
|
+
return this.groups$.value;
|
|
9
|
+
}
|
|
10
|
+
relays$ = new BehaviorSubject(new Map());
|
|
11
|
+
get relays() {
|
|
12
|
+
return this.relays$.value;
|
|
13
|
+
}
|
|
14
|
+
/** An array of relays to never connect to */
|
|
15
|
+
blacklist = new Set();
|
|
7
16
|
constructor(options) {
|
|
8
17
|
this.options = options;
|
|
9
18
|
}
|
|
19
|
+
filterBlacklist(urls) {
|
|
20
|
+
return urls.filter((url) => !this.blacklist.has(url));
|
|
21
|
+
}
|
|
10
22
|
/** Get or create a new relay connection */
|
|
11
23
|
relay(url) {
|
|
24
|
+
if (this.blacklist.has(url))
|
|
25
|
+
throw new Error("Relay is on blacklist");
|
|
12
26
|
let relay = this.relays.get(url);
|
|
13
27
|
if (relay)
|
|
14
28
|
return relay;
|
|
15
29
|
else {
|
|
16
30
|
relay = new Relay(url, this.options);
|
|
17
|
-
this.relays.set(url, relay);
|
|
31
|
+
this.relays$.next(this.relays.set(url, relay));
|
|
18
32
|
return relay;
|
|
19
33
|
}
|
|
20
34
|
}
|
|
21
35
|
/** Create a group of relays */
|
|
22
36
|
group(relays) {
|
|
37
|
+
relays = this.filterBlacklist(relays);
|
|
23
38
|
const key = relays.sort().join(",");
|
|
24
39
|
let group = this.groups.get(key);
|
|
25
40
|
if (group)
|
|
26
41
|
return group;
|
|
27
42
|
group = new RelayGroup(relays.map((url) => this.relay(url)));
|
|
28
|
-
this.groups.set(key, group);
|
|
43
|
+
this.groups$.next(this.groups.set(key, group));
|
|
29
44
|
return group;
|
|
30
45
|
}
|
|
31
46
|
/** Make a REQ to multiple relays that does not deduplicate events */
|
package/dist/relay.d.ts
CHANGED
|
@@ -28,15 +28,23 @@ export declare class Relay implements IRelay {
|
|
|
28
28
|
authenticated$: BehaviorSubject<boolean>;
|
|
29
29
|
/** The notices from the relay */
|
|
30
30
|
notices$: BehaviorSubject<string[]>;
|
|
31
|
+
/** The last connection error */
|
|
32
|
+
error$: BehaviorSubject<Error | null>;
|
|
33
|
+
/**
|
|
34
|
+
* A passive observable of all messages from the relay
|
|
35
|
+
* @note Subscribing to this will not connect to the relay
|
|
36
|
+
*/
|
|
37
|
+
message$: Observable<any>;
|
|
38
|
+
/**
|
|
39
|
+
* A passive observable of NOTICE messages from the relay
|
|
40
|
+
* @note Subscribing to this will not connect to the relay
|
|
41
|
+
*/
|
|
42
|
+
notice$: Observable<string>;
|
|
31
43
|
/** An observable that emits the NIP-11 information document for the relay */
|
|
32
44
|
information$: Observable<RelayInformation | null>;
|
|
33
45
|
protected _nip11: RelayInformation | null;
|
|
34
46
|
/** An observable that emits the limitations for the relay */
|
|
35
47
|
limitations$: Observable<RelayInformation["limitation"] | null>;
|
|
36
|
-
/** An observable of all messages from the relay */
|
|
37
|
-
message$: Observable<any>;
|
|
38
|
-
/** An observable of NOTICE messages from the relay */
|
|
39
|
-
notice$: Observable<string>;
|
|
40
48
|
get connected(): boolean;
|
|
41
49
|
get challenge(): string | null;
|
|
42
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";
|
|
@@ -27,15 +27,23 @@ export class Relay {
|
|
|
27
27
|
authenticated$ = new BehaviorSubject(false);
|
|
28
28
|
/** The notices from the relay */
|
|
29
29
|
notices$ = new BehaviorSubject([]);
|
|
30
|
+
/** The last connection error */
|
|
31
|
+
error$ = new BehaviorSubject(null);
|
|
32
|
+
/**
|
|
33
|
+
* A passive observable of all messages from the relay
|
|
34
|
+
* @note Subscribing to this will not connect to the relay
|
|
35
|
+
*/
|
|
36
|
+
message$;
|
|
37
|
+
/**
|
|
38
|
+
* A passive observable of NOTICE messages from the relay
|
|
39
|
+
* @note Subscribing to this will not connect to the relay
|
|
40
|
+
*/
|
|
41
|
+
notice$;
|
|
30
42
|
/** An observable that emits the NIP-11 information document for the relay */
|
|
31
43
|
information$;
|
|
32
44
|
_nip11 = null;
|
|
33
45
|
/** An observable that emits the limitations for the relay */
|
|
34
46
|
limitations$;
|
|
35
|
-
/** An observable of all messages from the relay */
|
|
36
|
-
message$;
|
|
37
|
-
/** An observable of NOTICE messages from the relay */
|
|
38
|
-
notice$;
|
|
39
47
|
// sync state
|
|
40
48
|
get connected() {
|
|
41
49
|
return this.connected$.value;
|
|
@@ -91,6 +99,7 @@ export class Relay {
|
|
|
91
99
|
this.log("Connected");
|
|
92
100
|
this.connected$.next(true);
|
|
93
101
|
this.attempts$.next(0);
|
|
102
|
+
this.error$.next(null);
|
|
94
103
|
this.resetState();
|
|
95
104
|
},
|
|
96
105
|
},
|
|
@@ -107,7 +116,6 @@ export class Relay {
|
|
|
107
116
|
},
|
|
108
117
|
WebSocketCtor: opts?.WebSocket,
|
|
109
118
|
});
|
|
110
|
-
this.message$ = this.socket.asObservable();
|
|
111
119
|
// Create an observable to fetch the NIP-11 information document
|
|
112
120
|
this.information$ = defer(() => {
|
|
113
121
|
this.log("Fetching NIP-11 information document");
|
|
@@ -123,19 +131,18 @@ export class Relay {
|
|
|
123
131
|
// Create observables that track if auth is required for REQ or EVENT
|
|
124
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));
|
|
125
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));
|
|
126
|
-
|
|
134
|
+
// Update the notices state
|
|
135
|
+
const listenForNotice = this.socket.pipe(
|
|
127
136
|
// listen for NOTICE messages
|
|
128
|
-
filter((m) => m[0] === "NOTICE"),
|
|
137
|
+
filter((m) => Array.isArray(m) && m[0] === "NOTICE"),
|
|
129
138
|
// pick the string out of the message
|
|
130
|
-
map((m) => m[1])
|
|
131
|
-
// Update the notices state
|
|
132
|
-
const notice = this.notice$.pipe(
|
|
139
|
+
map((m) => m[1]),
|
|
133
140
|
// Track all notices
|
|
134
141
|
scan((acc, notice) => [...acc, notice], []),
|
|
135
142
|
// Update the notices state
|
|
136
143
|
tap((notices) => this.notices$.next(notices)));
|
|
137
144
|
// Update the challenge state
|
|
138
|
-
const
|
|
145
|
+
const ListenForChallenge = this.socket.pipe(
|
|
139
146
|
// listen for AUTH messages
|
|
140
147
|
filter((message) => message[0] === "AUTH"),
|
|
141
148
|
// pick the challenge string out
|
|
@@ -145,12 +152,21 @@ export class Relay {
|
|
|
145
152
|
this.log("Received AUTH challenge", challenge);
|
|
146
153
|
this.challenge$.next(challenge);
|
|
147
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]));
|
|
148
164
|
// Merge all watchers
|
|
149
165
|
this.watchTower = this.ready$.pipe(switchMap((ready) => {
|
|
150
166
|
if (!ready)
|
|
151
167
|
return NEVER;
|
|
152
168
|
// Only start the watch tower if the relay is ready
|
|
153
|
-
return merge(
|
|
169
|
+
return merge(listenForAllMessages, listenForNotice, ListenForChallenge, this.information$).pipe(
|
|
154
170
|
// Never emit any values
|
|
155
171
|
ignoreElements(),
|
|
156
172
|
// Start the reconnect timer if the connection has an error
|
|
@@ -168,6 +184,7 @@ export class Relay {
|
|
|
168
184
|
startReconnectTimer(error) {
|
|
169
185
|
if (!this.ready$.value)
|
|
170
186
|
return;
|
|
187
|
+
this.error$.next(error instanceof Error ? error : new Error("Connection error"));
|
|
171
188
|
this.ready$.next(false);
|
|
172
189
|
this.reconnectTimer(error, this.attempts$.value)
|
|
173
190
|
.pipe(take(1))
|
|
@@ -187,13 +204,17 @@ export class Relay {
|
|
|
187
204
|
}
|
|
188
205
|
/** Wait for the relay to be ready to accept connections */
|
|
189
206
|
waitForReady(observable) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
207
|
+
// Don't wait if the relay is already ready
|
|
208
|
+
if (this.ready$.value)
|
|
209
|
+
return observable;
|
|
210
|
+
else
|
|
211
|
+
return this.ready$.pipe(
|
|
212
|
+
// wait for ready to be true
|
|
213
|
+
filter((ready) => ready),
|
|
214
|
+
// complete after the first value so this does not repeat
|
|
215
|
+
take(1),
|
|
216
|
+
// switch to the observable
|
|
217
|
+
switchMap(() => observable));
|
|
197
218
|
}
|
|
198
219
|
multiplex(open, close, filter) {
|
|
199
220
|
return this.socket.multiplex(open, close, filter);
|
|
@@ -234,7 +255,9 @@ export class Relay {
|
|
|
234
255
|
timeout({
|
|
235
256
|
first: this.eoseTimeout,
|
|
236
257
|
with: () => merge(of("EOSE"), NEVER),
|
|
237
|
-
})
|
|
258
|
+
}),
|
|
259
|
+
// Only create one upstream subscription
|
|
260
|
+
share());
|
|
238
261
|
// Wait for auth if required and make sure to start the watch tower
|
|
239
262
|
return this.waitForReady(this.waitForAuth(this.authRequiredForReq, observable));
|
|
240
263
|
}
|
|
@@ -264,7 +287,9 @@ export class Relay {
|
|
|
264
287
|
timeout({
|
|
265
288
|
first: this.eventTimeout,
|
|
266
289
|
with: () => of({ ok: false, from: this.url, message: "Timeout" }),
|
|
267
|
-
})
|
|
290
|
+
}),
|
|
291
|
+
// Only create one upstream subscription
|
|
292
|
+
share());
|
|
268
293
|
// skip wait for auth if verb is AUTH
|
|
269
294
|
if (verb === "AUTH")
|
|
270
295
|
return this.waitForReady(observable);
|
package/dist/types.d.ts
CHANGED
|
@@ -66,7 +66,7 @@ export interface IGroup extends Nip01Actions {
|
|
|
66
66
|
/** Send an EVENT message with retries */
|
|
67
67
|
publish(event: NostrEvent, opts?: {
|
|
68
68
|
retries?: number;
|
|
69
|
-
}): Observable<PublishResponse
|
|
69
|
+
}): Observable<PublishResponse>;
|
|
70
70
|
/** Send a REQ message with retries */
|
|
71
71
|
request(filters: Filter | Filter[], opts?: {
|
|
72
72
|
id?: string;
|
|
@@ -90,7 +90,7 @@ export interface IPool {
|
|
|
90
90
|
/** Send an EVENT message to relays with retries */
|
|
91
91
|
publish(relays: string[], event: NostrEvent, opts?: {
|
|
92
92
|
retries?: number;
|
|
93
|
-
}): Observable<PublishResponse
|
|
93
|
+
}): Observable<PublishResponse>;
|
|
94
94
|
/** Send a REQ message to relays with retries */
|
|
95
95
|
request(relays: string[], filters: Filter | Filter[], opts?: {
|
|
96
96
|
id?: string;
|