applesauce-relay 1.1.0 → 1.2.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__/exports.test.d.ts +1 -0
- package/dist/__tests__/exports.test.js +19 -0
- package/dist/__tests__/pool.test.js +64 -49
- package/dist/__tests__/relay.test.js +37 -4
- package/dist/group.d.ts +5 -5
- package/dist/operators/__tests__/exports.test.d.ts +1 -0
- package/dist/operators/__tests__/exports.test.js +15 -0
- package/dist/pool.d.ts +5 -5
- package/dist/pool.js +12 -5
- package/dist/relay.d.ts +2 -2
- package/dist/relay.js +19 -5
- package/dist/types.d.ts +11 -9
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import * as exports from "../index.js";
|
|
3
|
+
describe("exports", () => {
|
|
4
|
+
it("should export the expected functions", () => {
|
|
5
|
+
expect(Object.keys(exports).sort()).toMatchInlineSnapshot(`
|
|
6
|
+
[
|
|
7
|
+
"Relay",
|
|
8
|
+
"RelayGroup",
|
|
9
|
+
"RelayPool",
|
|
10
|
+
"ReqCloseError",
|
|
11
|
+
"completeOnEose",
|
|
12
|
+
"markFromRelay",
|
|
13
|
+
"onlyEvents",
|
|
14
|
+
"storeEvents",
|
|
15
|
+
"toEventStore",
|
|
16
|
+
]
|
|
17
|
+
`);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { expect, beforeEach, afterEach, describe, it } from "vitest";
|
|
2
2
|
import { subscribeSpyTo } from "@hirez_io/observer-spy";
|
|
3
3
|
import { WS } from "vitest-websocket-mock";
|
|
4
4
|
import { RelayPool } from "../pool.js";
|
|
@@ -27,55 +27,70 @@ afterEach(async () => {
|
|
|
27
27
|
// Clean up WebSocket mocks
|
|
28
28
|
await WS.clean();
|
|
29
29
|
});
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
describe("relay", () => {
|
|
31
|
+
it("should create a new relay", () => {
|
|
32
|
+
const url = "wss://relay1.example.com/";
|
|
33
|
+
const relay = pool.relay(url);
|
|
34
|
+
expect(relay).toBeDefined();
|
|
35
|
+
expect(pool.relays.get(url)).toBe(relay);
|
|
36
|
+
});
|
|
37
|
+
it("should return existing relay connection if already exists", () => {
|
|
38
|
+
const url = "wss://relay1.example.com";
|
|
39
|
+
const relay1 = pool.relay(url);
|
|
40
|
+
const relay2 = pool.relay(url);
|
|
41
|
+
expect(relay1).toBe(relay2);
|
|
42
|
+
expect(pool.relays.size).toBe(1);
|
|
43
|
+
});
|
|
44
|
+
it("should normalize relay urls", () => {
|
|
45
|
+
expect(pool.relay("wss://relay.example.com")).toBe(pool.relay("wss://relay.example.com/"));
|
|
46
|
+
expect(pool.relay("wss://relay.example.com:443")).toBe(pool.relay("wss://relay.example.com/"));
|
|
47
|
+
expect(pool.relay("ws://relay.example.com:80")).toBe(pool.relay("ws://relay.example.com/"));
|
|
48
|
+
});
|
|
35
49
|
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
describe("group", () => {
|
|
51
|
+
it("should create a relay group", () => {
|
|
52
|
+
const urls = ["wss://relay1.example.com/", "wss://relay2.example.com/"];
|
|
53
|
+
const group = pool.group(urls);
|
|
54
|
+
expect(group).toBeDefined();
|
|
55
|
+
expect(pool.groups.get(urls.sort().join(","))).toBe(group);
|
|
56
|
+
});
|
|
42
57
|
});
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
describe("req", () => {
|
|
59
|
+
it("should send subscription to multiple relays", async () => {
|
|
60
|
+
const urls = ["wss://relay1.example.com", "wss://relay2.example.com"];
|
|
61
|
+
const filters = { kinds: [1] };
|
|
62
|
+
const spy = subscribeSpyTo(pool.req(urls, filters));
|
|
63
|
+
// Verify REQ was sent to both relays
|
|
64
|
+
const req1 = await mockServer1.nextMessage;
|
|
65
|
+
const req2 = await mockServer2.nextMessage;
|
|
66
|
+
// Both messages should be REQ messages with the same filter
|
|
67
|
+
expect(JSON.parse(req1)[0]).toBe("REQ");
|
|
68
|
+
expect(JSON.parse(req2)[0]).toBe("REQ");
|
|
69
|
+
expect(JSON.parse(req1)[2]).toEqual(filters);
|
|
70
|
+
expect(JSON.parse(req2)[2]).toEqual(filters);
|
|
71
|
+
// Send EVENT from first relay
|
|
72
|
+
mockServer1.send(JSON.stringify(["EVENT", JSON.parse(req1)[1], mockEvent]));
|
|
73
|
+
// Send EOSE from both relays
|
|
74
|
+
mockServer1.send(JSON.stringify(["EOSE", JSON.parse(req1)[1]]));
|
|
75
|
+
mockServer2.send(JSON.stringify(["EOSE", JSON.parse(req2)[1]]));
|
|
76
|
+
expect(spy.getValues()).toContainEqual(expect.objectContaining(mockEvent));
|
|
77
|
+
});
|
|
48
78
|
});
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
expect(spy.getValues()).toContainEqual(expect.objectContaining(mockEvent));
|
|
67
|
-
});
|
|
68
|
-
test("event method publishes to multiple relays", async () => {
|
|
69
|
-
const urls = ["wss://relay1.example.com", "wss://relay2.example.com"];
|
|
70
|
-
const spy = subscribeSpyTo(pool.event(urls, mockEvent));
|
|
71
|
-
// Verify EVENT was sent to both relays
|
|
72
|
-
const event1 = await mockServer1.nextMessage;
|
|
73
|
-
const event2 = await mockServer2.nextMessage;
|
|
74
|
-
expect(JSON.parse(event1)).toEqual(["EVENT", mockEvent]);
|
|
75
|
-
expect(JSON.parse(event2)).toEqual(["EVENT", mockEvent]);
|
|
76
|
-
// Send OK responses from both relays
|
|
77
|
-
mockServer1.send(JSON.stringify(["OK", mockEvent.id, true, ""]));
|
|
78
|
-
mockServer2.send(JSON.stringify(["OK", mockEvent.id, true, ""]));
|
|
79
|
-
expect(spy.getValues()).toContainEqual({ ok: true, from: "wss://relay1.example.com", message: "" });
|
|
80
|
-
expect(spy.getValues()).toContainEqual({ ok: true, from: "wss://relay2.example.com", message: "" });
|
|
79
|
+
describe("event", () => {
|
|
80
|
+
it("should publish to multiple relays", async () => {
|
|
81
|
+
const urls = ["wss://relay1.example.com/", "wss://relay2.example.com/"];
|
|
82
|
+
const spy = subscribeSpyTo(pool.event(urls, mockEvent));
|
|
83
|
+
// Verify EVENT was sent to both relays
|
|
84
|
+
const event1 = await mockServer1.nextMessage;
|
|
85
|
+
const event2 = await mockServer2.nextMessage;
|
|
86
|
+
expect(JSON.parse(event1)).toEqual(["EVENT", mockEvent]);
|
|
87
|
+
expect(JSON.parse(event2)).toEqual(["EVENT", mockEvent]);
|
|
88
|
+
// Send OK responses from both relays
|
|
89
|
+
mockServer1.send(JSON.stringify(["OK", mockEvent.id, true, ""]));
|
|
90
|
+
mockServer2.send(JSON.stringify(["OK", mockEvent.id, true, ""]));
|
|
91
|
+
expect(spy.getValues()).toEqual([
|
|
92
|
+
{ ok: true, from: "wss://relay1.example.com/", message: "" },
|
|
93
|
+
{ ok: true, from: "wss://relay2.example.com/", message: "" },
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
81
96
|
});
|
|
@@ -4,7 +4,7 @@ import { getSeenRelays } from "applesauce-core/helpers";
|
|
|
4
4
|
import { WS } from "vitest-websocket-mock";
|
|
5
5
|
import { Relay } from "../relay.js";
|
|
6
6
|
import { filter } from "rxjs/operators";
|
|
7
|
-
import { firstValueFrom, of, throwError, timer } from "rxjs";
|
|
7
|
+
import { firstValueFrom, of, Subject, throwError, timer } from "rxjs";
|
|
8
8
|
const defaultMockInfo = {
|
|
9
9
|
name: "Test Relay",
|
|
10
10
|
description: "Test Relay Description",
|
|
@@ -49,9 +49,7 @@ describe("req", () => {
|
|
|
49
49
|
});
|
|
50
50
|
it("should send expected messages to relay", async () => {
|
|
51
51
|
subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
|
|
52
|
-
|
|
53
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
54
|
-
expect(server.messages).toEqual([["REQ", "sub1", { kinds: [1] }]]);
|
|
52
|
+
await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
|
|
55
53
|
});
|
|
56
54
|
it("should not close the REQ when EOSE is received", async () => {
|
|
57
55
|
// Create subscription that completes after first EOSE
|
|
@@ -210,6 +208,41 @@ describe("req", () => {
|
|
|
210
208
|
relay.ready$.next(true);
|
|
211
209
|
await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
|
|
212
210
|
});
|
|
211
|
+
it("should wait for filters if filters are provided as an observable", async () => {
|
|
212
|
+
const filters = new Subject();
|
|
213
|
+
subscribeSpyTo(relay.req(filters, "sub1"));
|
|
214
|
+
// Wait 10sm and ensure no messages were sent yet
|
|
215
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
216
|
+
expect(server.messagesToConsume.pendingItems.length).toBe(0);
|
|
217
|
+
// Send REQ message with filters
|
|
218
|
+
filters.next([{ kinds: [1] }]);
|
|
219
|
+
// Wait for the REQ message to be sent
|
|
220
|
+
await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
|
|
221
|
+
});
|
|
222
|
+
it("should update filters if filters are provided as an observable", async () => {
|
|
223
|
+
const filters = new Subject();
|
|
224
|
+
subscribeSpyTo(relay.req(filters, "sub1"));
|
|
225
|
+
// Send REQ message with filters
|
|
226
|
+
filters.next([{ kinds: [1] }]);
|
|
227
|
+
// Should send REQ message with new filters
|
|
228
|
+
await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
|
|
229
|
+
// Send REQ message with filters
|
|
230
|
+
filters.next([{ kinds: [2] }]);
|
|
231
|
+
// Should send new REQ message with new filters
|
|
232
|
+
await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [2] }]);
|
|
233
|
+
// It should not send CLOSE message
|
|
234
|
+
await expect(server.messages).not.toContain(["CLOSE", "sub1"]);
|
|
235
|
+
});
|
|
236
|
+
it("should complete if filters are provided as an observable that completes", async () => {
|
|
237
|
+
const filters = new Subject();
|
|
238
|
+
const sub = subscribeSpyTo(relay.req(filters, "sub1"));
|
|
239
|
+
// Send REQ message with filters
|
|
240
|
+
filters.next([{ kinds: [1] }]);
|
|
241
|
+
// Complete the observable
|
|
242
|
+
filters.complete();
|
|
243
|
+
await sub.onComplete();
|
|
244
|
+
expect(sub.receivedComplete()).toBe(true);
|
|
245
|
+
});
|
|
213
246
|
});
|
|
214
247
|
describe("event", () => {
|
|
215
248
|
it("should wait for authentication if relay responds with auth-required", async () => {
|
package/dist/group.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type NostrEvent } from "nostr-tools";
|
|
2
2
|
import { Observable } from "rxjs";
|
|
3
|
-
import { IGroup, IRelay, PublishResponse, SubscriptionResponse, PublishOptions, RequestOptions, SubscriptionOptions } from "./types.js";
|
|
3
|
+
import { IGroup, IRelay, PublishResponse, SubscriptionResponse, PublishOptions, RequestOptions, SubscriptionOptions, FilterInput } from "./types.js";
|
|
4
4
|
export declare class RelayGroup implements IGroup {
|
|
5
5
|
relays: IRelay[];
|
|
6
6
|
constructor(relays: IRelay[]);
|
|
7
7
|
/** Takes an array of observables and only emits EOSE when all observables have emitted EOSE */
|
|
8
8
|
protected mergeEOSE(...requests: Observable<SubscriptionResponse>[]): Observable<import("nostr-tools").Event | "EOSE">;
|
|
9
9
|
/** Make a request to all relays */
|
|
10
|
-
req(filters:
|
|
10
|
+
req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
|
|
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
14
|
publish(event: NostrEvent, opts?: PublishOptions): Observable<PublishResponse>;
|
|
15
15
|
/** Request events from all relays with retries ( default 3 retries ) */
|
|
16
|
-
request(filters:
|
|
16
|
+
request(filters: FilterInput, opts?: RequestOptions): Observable<NostrEvent>;
|
|
17
17
|
/** Open a subscription to all relays with retries ( default 3 retries ) */
|
|
18
|
-
subscription(filters:
|
|
18
|
+
subscription(filters: FilterInput, opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
|
|
19
19
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import * as exports from "../index.js";
|
|
3
|
+
describe("exports", () => {
|
|
4
|
+
it("should export the expected functions", () => {
|
|
5
|
+
expect(Object.keys(exports).sort()).toMatchInlineSnapshot(`
|
|
6
|
+
[
|
|
7
|
+
"completeOnEose",
|
|
8
|
+
"markFromRelay",
|
|
9
|
+
"onlyEvents",
|
|
10
|
+
"storeEvents",
|
|
11
|
+
"toEventStore",
|
|
12
|
+
]
|
|
13
|
+
`);
|
|
14
|
+
});
|
|
15
|
+
});
|
package/dist/pool.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type NostrEvent } from "nostr-tools";
|
|
2
2
|
import { BehaviorSubject, Observable } from "rxjs";
|
|
3
3
|
import { RelayGroup } from "./group.js";
|
|
4
4
|
import { Relay, RelayOptions } from "./relay.js";
|
|
5
|
-
import { IPool, PublishResponse, PublishOptions, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
|
|
5
|
+
import { IPool, PublishResponse, PublishOptions, RequestOptions, SubscriptionOptions, SubscriptionResponse, FilterInput } from "./types.js";
|
|
6
6
|
export declare class RelayPool implements IPool {
|
|
7
7
|
options?: RelayOptions | undefined;
|
|
8
8
|
groups$: BehaviorSubject<Map<string, RelayGroup>>;
|
|
@@ -18,13 +18,13 @@ export declare class RelayPool implements IPool {
|
|
|
18
18
|
/** Create a group of relays */
|
|
19
19
|
group(relays: string[]): RelayGroup;
|
|
20
20
|
/** Make a REQ to multiple relays that does not deduplicate events */
|
|
21
|
-
req(relays: string[], filters:
|
|
21
|
+
req(relays: string[], filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
|
|
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
25
|
publish(relays: string[], event: NostrEvent, opts?: PublishOptions): Observable<PublishResponse>;
|
|
26
26
|
/** Request events from multiple relays */
|
|
27
|
-
request(relays: string[], filters:
|
|
27
|
+
request(relays: string[], filters: FilterInput, opts?: RequestOptions): Observable<NostrEvent>;
|
|
28
28
|
/** Open a subscription to multiple relays */
|
|
29
|
-
subscription(relays: string[], filters:
|
|
29
|
+
subscription(relays: string[], filters: FilterInput, opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
|
|
30
30
|
}
|
package/dist/pool.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BehaviorSubject } from "rxjs";
|
|
2
2
|
import { RelayGroup } from "./group.js";
|
|
3
3
|
import { Relay } from "./relay.js";
|
|
4
|
+
import { normalizeURL } from "applesauce-core/helpers";
|
|
4
5
|
export class RelayPool {
|
|
5
6
|
options;
|
|
6
7
|
groups$ = new BehaviorSubject(new Map());
|
|
@@ -21,19 +22,25 @@ export class RelayPool {
|
|
|
21
22
|
}
|
|
22
23
|
/** Get or create a new relay connection */
|
|
23
24
|
relay(url) {
|
|
25
|
+
// Normalize the url
|
|
26
|
+
url = normalizeURL(url);
|
|
27
|
+
// Check if the url is blacklisted
|
|
24
28
|
if (this.blacklist.has(url))
|
|
25
29
|
throw new Error("Relay is on blacklist");
|
|
30
|
+
// Check if the relay already exists
|
|
26
31
|
let relay = this.relays.get(url);
|
|
27
32
|
if (relay)
|
|
28
33
|
return relay;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
+
// Create a new relay
|
|
35
|
+
relay = new Relay(url, this.options);
|
|
36
|
+
this.relays$.next(this.relays.set(url, relay));
|
|
37
|
+
return relay;
|
|
34
38
|
}
|
|
35
39
|
/** Create a group of relays */
|
|
36
40
|
group(relays) {
|
|
41
|
+
// Normalize all urls
|
|
42
|
+
relays = relays.map((url) => normalizeURL(url));
|
|
43
|
+
// Filter out any blacklisted relays
|
|
37
44
|
relays = this.filterBlacklist(relays);
|
|
38
45
|
const key = relays.sort().join(",");
|
|
39
46
|
let group = this.groups.get(key);
|
package/dist/relay.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type Filter, type NostrEvent } from "nostr-tools";
|
|
|
3
3
|
import { BehaviorSubject, Observable } from "rxjs";
|
|
4
4
|
import { WebSocketSubject, WebSocketSubjectConfig } from "rxjs/webSocket";
|
|
5
5
|
import { RelayInformation } from "nostr-tools/nip11";
|
|
6
|
-
import { AuthSigner, IRelay, PublishOptions, PublishResponse, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
|
|
6
|
+
import { AuthSigner, FilterInput, IRelay, PublishOptions, PublishResponse, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
|
|
7
7
|
/** An error that is thrown when a REQ is closed from the relay side */
|
|
8
8
|
export declare class ReqCloseError extends Error {
|
|
9
9
|
}
|
|
@@ -74,7 +74,7 @@ export declare class Relay implements IRelay {
|
|
|
74
74
|
/** Send a message to the relay */
|
|
75
75
|
next(message: any): void;
|
|
76
76
|
/** Create a REQ observable that emits events or "EOSE" or errors */
|
|
77
|
-
req(filters:
|
|
77
|
+
req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
|
|
78
78
|
/** Send an EVENT or AUTH message and return an observable of PublishResponse that completes or errors */
|
|
79
79
|
event(event: NostrEvent, verb?: "EVENT" | "AUTH"): Observable<PublishResponse>;
|
|
80
80
|
/** send and AUTH message */
|
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, Subject, switchMap, take, tap, throwError, timeout, timer, } from "rxjs";
|
|
5
|
+
import { BehaviorSubject, catchError, combineLatest, defer, endWith, filter, finalize, from, ignoreElements, isObservable, map, merge, mergeMap, 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 { completeOnEose } from "./operators/complete-on-eose.js";
|
|
8
8
|
import { markFromRelay } from "./operators/mark-from-relay.js";
|
|
@@ -225,10 +225,24 @@ export class Relay {
|
|
|
225
225
|
}
|
|
226
226
|
/** Create a REQ observable that emits events or "EOSE" or errors */
|
|
227
227
|
req(filters, id = nanoid()) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
228
|
+
// Convert filters input into an observable, if its a normal value merge it with NEVER so it never completes
|
|
229
|
+
const input = isObservable(filters) ? filters : merge(of(filters), NEVER);
|
|
230
|
+
// Create an observable that completes when the upstream observable completes
|
|
231
|
+
const complete = input.pipe(ignoreElements(), endWith(null));
|
|
232
|
+
// Create an observable that filters responses from the relay to just the ones for this REQ
|
|
233
|
+
const messages = this.socket.pipe(filter((m) => Array.isArray(m) && (m[0] === "EVENT" || m[0] === "CLOSED" || m[0] === "EOSE") && m[1] === id));
|
|
234
|
+
// Create an observable that controls sending the filters and closing the REQ
|
|
235
|
+
const control = input.pipe(
|
|
236
|
+
// Send the filters when they change
|
|
237
|
+
tap((filters) => this.socket.next(Array.isArray(filters) ? ["REQ", id, ...filters] : ["REQ", id, filters])),
|
|
238
|
+
// Close the req when unsubscribed
|
|
239
|
+
finalize(() => this.socket.next(["CLOSE", id])),
|
|
240
|
+
// Once filters have been sent, switch to listening for messages
|
|
241
|
+
switchMap(() => messages));
|
|
242
|
+
// Start the watch tower with the observables
|
|
243
|
+
const observable = merge(this.watchTower, control).pipe(
|
|
244
|
+
// Complete the subscription when the input is completed
|
|
245
|
+
takeUntil(complete),
|
|
232
246
|
// Map the messages to events, EOSE, or throw an error
|
|
233
247
|
map((message) => {
|
|
234
248
|
if (message[0] === "EOSE")
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventTemplate, Filter, NostrEvent } from "nostr-tools";
|
|
1
|
+
import { type EventTemplate, type Filter, type NostrEvent } from "nostr-tools";
|
|
2
2
|
import { Observable } from "rxjs";
|
|
3
3
|
import { WebSocketSubject } from "rxjs/webSocket";
|
|
4
4
|
export type SubscriptionResponse = NostrEvent | "EOSE";
|
|
@@ -28,11 +28,13 @@ export type SubscriptionOptions = {
|
|
|
28
28
|
export type AuthSigner = {
|
|
29
29
|
signEvent: (event: EventTemplate) => NostrEvent | Promise<NostrEvent>;
|
|
30
30
|
};
|
|
31
|
+
/** The type of input the REQ method accepts */
|
|
32
|
+
export type FilterInput = Filter | Filter[] | Observable<Filter | Filter[]>;
|
|
31
33
|
export interface Nip01Actions {
|
|
32
34
|
/** Send an EVENT message */
|
|
33
35
|
event(event: NostrEvent): Observable<PublishResponse>;
|
|
34
36
|
/** Send a REQ message */
|
|
35
|
-
req(filters:
|
|
37
|
+
req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
|
|
36
38
|
}
|
|
37
39
|
export interface IRelay extends MultiplexWebSocket, Nip01Actions, IRelayState {
|
|
38
40
|
url: string;
|
|
@@ -52,12 +54,12 @@ export interface IRelay extends MultiplexWebSocket, Nip01Actions, IRelayState {
|
|
|
52
54
|
retries?: number;
|
|
53
55
|
}): Observable<PublishResponse>;
|
|
54
56
|
/** Send a REQ message with retries */
|
|
55
|
-
request(filters:
|
|
57
|
+
request(filters: FilterInput, opts?: {
|
|
56
58
|
id?: string;
|
|
57
59
|
retries?: number;
|
|
58
60
|
}): Observable<NostrEvent>;
|
|
59
61
|
/** Open a subscription with retries */
|
|
60
|
-
subscription(filters:
|
|
62
|
+
subscription(filters: FilterInput, opts?: {
|
|
61
63
|
id?: string;
|
|
62
64
|
retries?: number;
|
|
63
65
|
}): Observable<SubscriptionResponse>;
|
|
@@ -68,12 +70,12 @@ export interface IGroup extends Nip01Actions {
|
|
|
68
70
|
retries?: number;
|
|
69
71
|
}): Observable<PublishResponse>;
|
|
70
72
|
/** Send a REQ message with retries */
|
|
71
|
-
request(filters:
|
|
73
|
+
request(filters: FilterInput, opts?: {
|
|
72
74
|
id?: string;
|
|
73
75
|
retries?: number;
|
|
74
76
|
}): Observable<NostrEvent>;
|
|
75
77
|
/** Open a subscription with retries */
|
|
76
|
-
subscription(filters:
|
|
78
|
+
subscription(filters: FilterInput, opts?: {
|
|
77
79
|
id?: string;
|
|
78
80
|
retries?: number;
|
|
79
81
|
}): Observable<SubscriptionResponse>;
|
|
@@ -82,7 +84,7 @@ export interface IPool {
|
|
|
82
84
|
/** Send an EVENT message */
|
|
83
85
|
event(relays: string[], event: NostrEvent): Observable<PublishResponse>;
|
|
84
86
|
/** Send a REQ message */
|
|
85
|
-
req(relays: string[], filters:
|
|
87
|
+
req(relays: string[], filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
|
|
86
88
|
/** Get or create a relay */
|
|
87
89
|
relay(url: string): IRelay;
|
|
88
90
|
/** Create a relay group */
|
|
@@ -92,12 +94,12 @@ export interface IPool {
|
|
|
92
94
|
retries?: number;
|
|
93
95
|
}): Observable<PublishResponse>;
|
|
94
96
|
/** Send a REQ message to relays with retries */
|
|
95
|
-
request(relays: string[], filters:
|
|
97
|
+
request(relays: string[], filters: FilterInput, opts?: {
|
|
96
98
|
id?: string;
|
|
97
99
|
retries?: number;
|
|
98
100
|
}): Observable<NostrEvent>;
|
|
99
101
|
/** Open a subscription to relays with retries */
|
|
100
|
-
subscription(relays: string[], filters:
|
|
102
|
+
subscription(relays: string[], filters: FilterInput, opts?: {
|
|
101
103
|
id?: string;
|
|
102
104
|
retries?: number;
|
|
103
105
|
}): Observable<SubscriptionResponse>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-relay",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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": "^1.
|
|
57
|
+
"applesauce-core": "^1.2.0",
|
|
58
58
|
"nanoid": "^5.0.9",
|
|
59
59
|
"nostr-tools": "^2.10.4",
|
|
60
60
|
"rxjs": "^7.8.1"
|