applesauce-relay 0.12.0 → 1.0.1
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 +115 -0
- package/dist/__tests__/auth.test.d.ts +1 -0
- package/dist/__tests__/auth.test.js +111 -0
- package/dist/__tests__/group.test.d.ts +1 -0
- package/dist/__tests__/group.test.js +106 -0
- package/dist/__tests__/pool.test.d.ts +1 -0
- package/dist/__tests__/pool.test.js +81 -0
- package/dist/__tests__/relay.test.d.ts +1 -0
- package/dist/__tests__/relay.test.js +561 -0
- package/dist/group.d.ts +19 -0
- package/dist/group.js +54 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/lib/negentropy.d.ts +61 -0
- package/dist/lib/negentropy.js +533 -0
- package/dist/negentropy.d.ts +15 -0
- package/dist/negentropy.js +68 -0
- package/dist/operators/complete-on-eose.d.ts +6 -0
- package/dist/operators/complete-on-eose.js +7 -0
- package/dist/operators/index.d.ts +4 -1
- package/dist/operators/index.js +4 -1
- package/dist/operators/mark-from-relay.d.ts +1 -1
- package/dist/operators/only-events.d.ts +1 -1
- package/dist/operators/store-events.d.ts +5 -0
- package/dist/operators/store-events.js +7 -0
- package/dist/operators/to-event-store.d.ts +6 -0
- package/dist/operators/to-event-store.js +19 -0
- package/dist/pool.d.ts +18 -5
- package/dist/pool.js +33 -23
- package/dist/relay.d.ts +73 -22
- package/dist/relay.js +278 -59
- package/dist/types.d.ts +104 -0
- package/dist/types.js +1 -0
- package/package.json +28 -6
package/README.md
CHANGED
|
@@ -1,3 +1,118 @@
|
|
|
1
1
|
# Applesauce Relay
|
|
2
2
|
|
|
3
3
|
`applesauce-relay` is a nostr relay communication framework built on top of [RxJS](https://rxjs.dev/)
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install applesauce-relay
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- [x] NIP-01
|
|
14
|
+
- [x] Relay pool and groups
|
|
15
|
+
- [x] Fetch NIP-11 information before connecting
|
|
16
|
+
- [x] NIP-11 `auth_required` limitation
|
|
17
|
+
- [ ] NIP-11 `max_subscriptions` limitation
|
|
18
|
+
- [x] Client negentropy sync
|
|
19
|
+
- [x] Reconnection backoff logic
|
|
20
|
+
- [x] republish event on reconnect and auth-required
|
|
21
|
+
- [x] Resubscribe on reconnect and auth-required
|
|
22
|
+
- [ ] NIP-45 COUNT
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
Read the [documentation](https://hzrd149.github.io/applesauce/overview/relays.html) for more detailed explanation of all methods
|
|
27
|
+
|
|
28
|
+
### Single Relay
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { Relay } from "./relay";
|
|
32
|
+
|
|
33
|
+
// Connect to a single relay
|
|
34
|
+
const relay = new Relay("wss://relay.example.com");
|
|
35
|
+
|
|
36
|
+
// Subscribe to events
|
|
37
|
+
relay
|
|
38
|
+
.req({
|
|
39
|
+
kinds: [1],
|
|
40
|
+
limit: 10,
|
|
41
|
+
})
|
|
42
|
+
.subscribe((response) => {
|
|
43
|
+
if (response === "EOSE") {
|
|
44
|
+
console.log("End of stored events");
|
|
45
|
+
} else {
|
|
46
|
+
console.log("Received event:", response);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Publish an event
|
|
51
|
+
const event = {
|
|
52
|
+
kind: 1,
|
|
53
|
+
content: "Hello Nostr!",
|
|
54
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
55
|
+
tags: [],
|
|
56
|
+
// ... other required fields
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
relay.event(event).subscribe((response) => {
|
|
60
|
+
console.log(`Published:`, response.ok);
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Relay Pool
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { RelayPool } from "./pool";
|
|
68
|
+
|
|
69
|
+
// Create a pool and connect to multiple relays
|
|
70
|
+
const pool = new RelayPool();
|
|
71
|
+
const relays = ["wss://relay1.example.com", "wss://relay2.example.com"];
|
|
72
|
+
|
|
73
|
+
// Subscribe to events from multiple relays
|
|
74
|
+
pool
|
|
75
|
+
.req(relays, {
|
|
76
|
+
kinds: [1],
|
|
77
|
+
limit: 10,
|
|
78
|
+
})
|
|
79
|
+
.subscribe((response) => {
|
|
80
|
+
if (response === "EOSE") {
|
|
81
|
+
console.log("End of stored events");
|
|
82
|
+
} else {
|
|
83
|
+
console.log("Received event:", response);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Publish to multiple relays
|
|
88
|
+
pool.event(relays, event).subscribe((response) => {
|
|
89
|
+
console.log(`Published to ${response.from}:`, response.ok);
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Relay Group
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { RelayPool } from "./pool";
|
|
97
|
+
|
|
98
|
+
const pool = new RelayPool();
|
|
99
|
+
const relays = ["wss://relay1.example.com", "wss://relay2.example.com"];
|
|
100
|
+
|
|
101
|
+
// Create a group (automatically deduplicates events)
|
|
102
|
+
const group = pool.group(relays);
|
|
103
|
+
|
|
104
|
+
// Subscribe to events
|
|
105
|
+
group
|
|
106
|
+
.req({
|
|
107
|
+
kinds: [1],
|
|
108
|
+
limit: 10,
|
|
109
|
+
})
|
|
110
|
+
.subscribe((response) => {
|
|
111
|
+
console.log("Received:", response);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Publish to all relays in group
|
|
115
|
+
group.event(event).subscribe((response) => {
|
|
116
|
+
console.log(`Published to ${response.from}:`, response.ok);
|
|
117
|
+
});
|
|
118
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { subscribeSpyTo } from "@hirez_io/observer-spy";
|
|
3
|
+
import { WS } from "vitest-websocket-mock";
|
|
4
|
+
import { Relay } from "../relay.js";
|
|
5
|
+
let server;
|
|
6
|
+
let relay;
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
server = new WS("wss://test", { jsonProtocol: true });
|
|
9
|
+
relay = new Relay("wss://test");
|
|
10
|
+
// Create a persistent subscription to keep the connection open
|
|
11
|
+
// @ts-expect-error
|
|
12
|
+
subscribeSpyTo(relay.socket);
|
|
13
|
+
});
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
server.close();
|
|
16
|
+
// Wait for server to close to prevent memory leaks
|
|
17
|
+
await WS.clean();
|
|
18
|
+
});
|
|
19
|
+
const mockEvent = {
|
|
20
|
+
kind: 1,
|
|
21
|
+
id: "00007641c9c3e65a71843933a44a18060c7c267a4f9169efa3735ece45c8f621",
|
|
22
|
+
pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
|
|
23
|
+
created_at: 1743712795,
|
|
24
|
+
tags: [["nonce", "13835058055282167643", "16"]],
|
|
25
|
+
content: "This is just stupid: https://codestr.fiatjaf.com/",
|
|
26
|
+
sig: "5a57b5a12bba4b7cf0121077b1421cf4df402c5c221376c076204fc4f7519e28ce6508f26ddc132c406ccfe6e62cc6db857b96c788565cdca9674fe9a0710ac2",
|
|
27
|
+
};
|
|
28
|
+
describe("event", () => {
|
|
29
|
+
it("should wait for auth before sending EVENT if auth-required received", async () => {
|
|
30
|
+
// Create first event subscription
|
|
31
|
+
const spy1 = subscribeSpyTo(relay.event(mockEvent));
|
|
32
|
+
// Verify EVENT was sent
|
|
33
|
+
const firstEventMessage = await server.nextMessage;
|
|
34
|
+
expect(firstEventMessage).toEqual(["EVENT", mockEvent]);
|
|
35
|
+
// Send auth-required response
|
|
36
|
+
server.send(["OK", mockEvent.id, false, "auth-required: need to authenticate"]);
|
|
37
|
+
// Create second event subscription - this should not send EVENT yet
|
|
38
|
+
const spy2 = subscribeSpyTo(relay.event(mockEvent));
|
|
39
|
+
// Should not have received any messages
|
|
40
|
+
expect(server.messages.length).toBe(1);
|
|
41
|
+
// Send AUTH challenge
|
|
42
|
+
server.send(["AUTH", "challenge-string"]);
|
|
43
|
+
// Send auth event
|
|
44
|
+
const authEvent = { ...mockEvent, id: "auth-id" };
|
|
45
|
+
subscribeSpyTo(relay.auth(authEvent));
|
|
46
|
+
// Verify AUTH was sent
|
|
47
|
+
const authMessage = await server.nextMessage;
|
|
48
|
+
expect(authMessage).toEqual(["AUTH", authEvent]);
|
|
49
|
+
// Send successful auth response
|
|
50
|
+
server.send(["OK", authEvent.id, true, ""]);
|
|
51
|
+
// Now the second EVENT should be sent
|
|
52
|
+
const secondEventMessage = await server.nextMessage;
|
|
53
|
+
expect(secondEventMessage).toEqual(["EVENT", mockEvent]);
|
|
54
|
+
// Send OK response for second event
|
|
55
|
+
server.send(["OK", mockEvent.id, true, ""]);
|
|
56
|
+
expect(spy1.getLastValue()).toEqual({
|
|
57
|
+
ok: false,
|
|
58
|
+
message: "auth-required: need to authenticate",
|
|
59
|
+
from: "wss://test",
|
|
60
|
+
});
|
|
61
|
+
expect(spy2.getLastValue()).toEqual({ ok: true, message: "", from: "wss://test" });
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe("req", () => {
|
|
65
|
+
it("should wait for auth before sending REQ if auth-required received", async () => {
|
|
66
|
+
// Create first REQ subscription
|
|
67
|
+
const filters = [{ kinds: [1], limit: 10 }];
|
|
68
|
+
subscribeSpyTo(relay.req(filters, "sub1"), { expectErrors: true });
|
|
69
|
+
// Verify REQ was sent
|
|
70
|
+
const firstReqMessage = await server.nextMessage;
|
|
71
|
+
expect(firstReqMessage).toEqual(["REQ", "sub1", ...filters]);
|
|
72
|
+
// Send auth-required response
|
|
73
|
+
server.send(["CLOSED", "sub1", "auth-required: need to authenticate"]);
|
|
74
|
+
// Consume the client CLOSE message for sub1
|
|
75
|
+
await server.nextMessage;
|
|
76
|
+
// Create second REQ subscription - this should not send REQ yet
|
|
77
|
+
subscribeSpyTo(relay.req(filters, "sub2"), { expectErrors: true });
|
|
78
|
+
// Should not have received any messages
|
|
79
|
+
expect(server.messages).not.toContain(["REQ", "sub2", ...filters]);
|
|
80
|
+
// Send AUTH challenge
|
|
81
|
+
server.send(["AUTH", "challenge-string"]);
|
|
82
|
+
// Send auth event
|
|
83
|
+
const authEvent = { ...mockEvent, id: "auth-id" };
|
|
84
|
+
subscribeSpyTo(relay.auth(authEvent));
|
|
85
|
+
// Verify AUTH was sent
|
|
86
|
+
const authMessage = await server.nextMessage;
|
|
87
|
+
expect(authMessage).toEqual(["AUTH", authEvent]);
|
|
88
|
+
// Send successful auth response
|
|
89
|
+
server.send(["OK", authEvent.id, true, ""]);
|
|
90
|
+
// Now the second REQ should be sent
|
|
91
|
+
const secondReqMessage = await server.nextMessage;
|
|
92
|
+
expect(secondReqMessage).toEqual(["REQ", "sub2", ...filters]);
|
|
93
|
+
// Send some events for the second subscription
|
|
94
|
+
server.send(["EVENT", "sub2", mockEvent]);
|
|
95
|
+
server.send(["EOSE", "sub2"]);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe("auth", () => {
|
|
99
|
+
it("should set authenticated state after successful AUTH challenge response", async () => {
|
|
100
|
+
// Send AUTH challenge
|
|
101
|
+
server.send(["AUTH", "challenge-string"]);
|
|
102
|
+
// Send auth event response
|
|
103
|
+
const authEvent = { ...mockEvent, id: "auth-id" };
|
|
104
|
+
subscribeSpyTo(relay.auth(authEvent));
|
|
105
|
+
// Verify AUTH was sent
|
|
106
|
+
await expect(server).toReceiveMessage(["AUTH", authEvent]);
|
|
107
|
+
// Send successful auth response
|
|
108
|
+
server.send(["OK", authEvent.id, true, ""]);
|
|
109
|
+
expect(relay.authenticated).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { subscribeSpyTo } from "@hirez_io/observer-spy";
|
|
3
|
+
import { WS } from "vitest-websocket-mock";
|
|
4
|
+
import { Relay } from "../relay.js";
|
|
5
|
+
import { RelayGroup } from "../group.js";
|
|
6
|
+
import { of } from "rxjs";
|
|
7
|
+
let mockRelay1;
|
|
8
|
+
let mockRelay2;
|
|
9
|
+
let relay1;
|
|
10
|
+
let relay2;
|
|
11
|
+
let group;
|
|
12
|
+
let mockEvent;
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
// Create mock relays
|
|
15
|
+
mockRelay1 = new WS("wss://relay1.test", { jsonProtocol: true });
|
|
16
|
+
mockRelay2 = new WS("wss://relay2.test", { jsonProtocol: true });
|
|
17
|
+
// Mock empty information document
|
|
18
|
+
vi.spyOn(Relay, "fetchInformationDocument").mockImplementation(() => of(null));
|
|
19
|
+
// Create relays
|
|
20
|
+
relay1 = new Relay("wss://relay1.test");
|
|
21
|
+
relay2 = new Relay("wss://relay2.test");
|
|
22
|
+
// Create group
|
|
23
|
+
group = new RelayGroup([relay1, relay2]);
|
|
24
|
+
mockEvent = {
|
|
25
|
+
kind: 1,
|
|
26
|
+
id: "test-id",
|
|
27
|
+
pubkey: "test-pubkey",
|
|
28
|
+
created_at: 1234567890,
|
|
29
|
+
tags: [],
|
|
30
|
+
content: "test content",
|
|
31
|
+
sig: "test-sig",
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
afterEach(async () => {
|
|
35
|
+
mockRelay1.close();
|
|
36
|
+
mockRelay2.close();
|
|
37
|
+
await WS.clean();
|
|
38
|
+
});
|
|
39
|
+
describe("req", () => {
|
|
40
|
+
it("should make requests to multiple relays", async () => {
|
|
41
|
+
group.req([{ kinds: [1] }], "test-sub").subscribe();
|
|
42
|
+
await expect(mockRelay1).toReceiveMessage(["REQ", "test-sub", { kinds: [1] }]);
|
|
43
|
+
await expect(mockRelay2).toReceiveMessage(["REQ", "test-sub", { kinds: [1] }]);
|
|
44
|
+
});
|
|
45
|
+
it("should emit events from all relays", async () => {
|
|
46
|
+
const spy = subscribeSpyTo(group.req([{ kinds: [1] }], "test-sub"));
|
|
47
|
+
await expect(mockRelay1).toReceiveMessage(["REQ", "test-sub", { kinds: [1] }]);
|
|
48
|
+
await expect(mockRelay2).toReceiveMessage(["REQ", "test-sub", { kinds: [1] }]);
|
|
49
|
+
mockRelay1.send(["EVENT", "test-sub", { ...mockEvent, id: "1" }]);
|
|
50
|
+
mockRelay2.send(["EVENT", "test-sub", { ...mockEvent, id: "2" }]);
|
|
51
|
+
expect(spy.getValues()).toEqual([expect.objectContaining({ id: "1" }), expect.objectContaining({ id: "2" })]);
|
|
52
|
+
});
|
|
53
|
+
it("should only emit EOSE once all relays have emitted EOSE", async () => {
|
|
54
|
+
const spy = subscribeSpyTo(group.req([{ kinds: [1] }], "test-sub"));
|
|
55
|
+
mockRelay1.send(["EOSE", "test-sub"]);
|
|
56
|
+
expect(spy.getValues()).not.toContain("EOSE");
|
|
57
|
+
mockRelay2.send(["EOSE", "test-sub"]);
|
|
58
|
+
expect(spy.getValues()).toContain("EOSE");
|
|
59
|
+
});
|
|
60
|
+
it("should ignore relays that have an error", async () => {
|
|
61
|
+
const spy = subscribeSpyTo(group.req([{ kinds: [1] }], "test-sub"));
|
|
62
|
+
mockRelay1.error();
|
|
63
|
+
mockRelay2.send(["EVENT", "test-sub", mockEvent]);
|
|
64
|
+
mockRelay2.send(["EOSE", "test-sub"]);
|
|
65
|
+
expect(spy.getValues()).toEqual([expect.objectContaining(mockEvent), "EOSE"]);
|
|
66
|
+
});
|
|
67
|
+
it("should emit EOSE if all relays error", async () => {
|
|
68
|
+
const spy = subscribeSpyTo(group.req([{ kinds: [1] }], "test-sub"));
|
|
69
|
+
mockRelay1.error();
|
|
70
|
+
mockRelay2.error();
|
|
71
|
+
expect(spy.getValues()).toEqual(["EOSE"]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe("event", () => {
|
|
75
|
+
it("should send EVENT to all relays in the group", async () => {
|
|
76
|
+
group.event(mockEvent).subscribe();
|
|
77
|
+
await expect(mockRelay1).toReceiveMessage(["EVENT", mockEvent]);
|
|
78
|
+
await expect(mockRelay2).toReceiveMessage(["EVENT", mockEvent]);
|
|
79
|
+
});
|
|
80
|
+
it("should emit OK messages from all relays", async () => {
|
|
81
|
+
const spy = subscribeSpyTo(group.event(mockEvent));
|
|
82
|
+
mockRelay1.send(["OK", mockEvent.id, true, ""]);
|
|
83
|
+
mockRelay2.send(["OK", mockEvent.id, true, ""]);
|
|
84
|
+
expect(spy.getValues()).toEqual([
|
|
85
|
+
expect.objectContaining({ ok: true, from: "wss://relay1.test", message: "" }),
|
|
86
|
+
expect.objectContaining({ ok: true, from: "wss://relay2.test", message: "" }),
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
it("should complete when all relays have sent OK messages", async () => {
|
|
90
|
+
const spy = subscribeSpyTo(group.event(mockEvent));
|
|
91
|
+
mockRelay1.send(["OK", mockEvent.id, true, ""]);
|
|
92
|
+
expect(spy.receivedComplete()).toBe(false);
|
|
93
|
+
mockRelay2.send(["OK", mockEvent.id, true, ""]);
|
|
94
|
+
expect(spy.receivedComplete()).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
it("should handle relay errors and still complete", async () => {
|
|
97
|
+
const spy = subscribeSpyTo(group.event(mockEvent));
|
|
98
|
+
mockRelay1.error();
|
|
99
|
+
mockRelay2.send(["OK", mockEvent.id, true, ""]);
|
|
100
|
+
expect(spy.getValues()).toEqual([
|
|
101
|
+
expect.objectContaining({ ok: false, from: "wss://relay1.test", message: "Unknown error" }),
|
|
102
|
+
expect.objectContaining({ ok: true, from: "wss://relay2.test", message: "" }),
|
|
103
|
+
]);
|
|
104
|
+
expect(spy.receivedComplete()).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { test, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { subscribeSpyTo } from "@hirez_io/observer-spy";
|
|
3
|
+
import { WS } from "vitest-websocket-mock";
|
|
4
|
+
import { RelayPool } from "../pool.js";
|
|
5
|
+
let pool;
|
|
6
|
+
let mockServer1;
|
|
7
|
+
let mockServer2;
|
|
8
|
+
let mockEvent;
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
// Create mock WebSocket servers
|
|
11
|
+
mockServer1 = new WS("wss://relay1.example.com");
|
|
12
|
+
mockServer2 = new WS("wss://relay2.example.com");
|
|
13
|
+
pool = new RelayPool();
|
|
14
|
+
mockEvent = {
|
|
15
|
+
kind: 1,
|
|
16
|
+
id: "test-id",
|
|
17
|
+
pubkey: "test-pubkey",
|
|
18
|
+
created_at: 1743712795,
|
|
19
|
+
tags: [],
|
|
20
|
+
content: "test content",
|
|
21
|
+
sig: "test-sig",
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
afterEach(async () => {
|
|
25
|
+
mockServer1.close();
|
|
26
|
+
mockServer2.close();
|
|
27
|
+
// Clean up WebSocket mocks
|
|
28
|
+
await WS.clean();
|
|
29
|
+
});
|
|
30
|
+
test("creates new relay connections", () => {
|
|
31
|
+
const url = "wss://relay1.example.com";
|
|
32
|
+
const relay = pool.relay(url);
|
|
33
|
+
expect(relay).toBeDefined();
|
|
34
|
+
expect(pool.relays.get(url)).toBe(relay);
|
|
35
|
+
});
|
|
36
|
+
test("returns existing relay connection if already exists", () => {
|
|
37
|
+
const url = "wss://relay1.example.com";
|
|
38
|
+
const relay1 = pool.relay(url);
|
|
39
|
+
const relay2 = pool.relay(url);
|
|
40
|
+
expect(relay1).toBe(relay2);
|
|
41
|
+
expect(pool.relays.size).toBe(1);
|
|
42
|
+
});
|
|
43
|
+
test("creates relay group with multiple relays", () => {
|
|
44
|
+
const urls = ["wss://relay1.example.com", "wss://relay2.example.com"];
|
|
45
|
+
const group = pool.group(urls);
|
|
46
|
+
expect(group).toBeDefined();
|
|
47
|
+
expect(pool.groups.get(urls.sort().join(","))).toBe(group);
|
|
48
|
+
});
|
|
49
|
+
test("req method sends subscription to multiple relays", async () => {
|
|
50
|
+
const urls = ["wss://relay1.example.com", "wss://relay2.example.com"];
|
|
51
|
+
const filters = { kinds: [1] };
|
|
52
|
+
const spy = subscribeSpyTo(pool.req(urls, filters));
|
|
53
|
+
// Verify REQ was sent to both relays
|
|
54
|
+
const req1 = await mockServer1.nextMessage;
|
|
55
|
+
const req2 = await mockServer2.nextMessage;
|
|
56
|
+
// Both messages should be REQ messages with the same filter
|
|
57
|
+
expect(JSON.parse(req1)[0]).toBe("REQ");
|
|
58
|
+
expect(JSON.parse(req2)[0]).toBe("REQ");
|
|
59
|
+
expect(JSON.parse(req1)[2]).toEqual(filters);
|
|
60
|
+
expect(JSON.parse(req2)[2]).toEqual(filters);
|
|
61
|
+
// Send EVENT from first relay
|
|
62
|
+
mockServer1.send(JSON.stringify(["EVENT", JSON.parse(req1)[1], mockEvent]));
|
|
63
|
+
// Send EOSE from both relays
|
|
64
|
+
mockServer1.send(JSON.stringify(["EOSE", JSON.parse(req1)[1]]));
|
|
65
|
+
mockServer2.send(JSON.stringify(["EOSE", JSON.parse(req2)[1]]));
|
|
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: "" });
|
|
81
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|