applesauce-relay 0.0.0-next-20250414120207 → 0.0.0-next-20250423145737
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 +70 -32
- package/dist/relay.d.ts +4 -2
- package/dist/relay.js +6 -4
- package/package.json +2 -2
- package/dist/__tests__/faker.d.ts +0 -19
- package/dist/__tests__/faker.js +0 -56
- package/dist/__tests__/matchers.d.ts +0 -60
- package/dist/__tests__/matchers.js +0 -177
- package/dist/__tests__/mock.d.ts +0 -84
- package/dist/__tests__/mock.js +0 -181
- package/dist/__tests__/spy.d.ts +0 -7
- package/dist/__tests__/spy.js +0 -65
- package/dist/__tests__/utils.d.ts +0 -3
- package/dist/__tests__/utils.js +0 -8
- package/dist/server/connection.d.ts +0 -1
- package/dist/server/connection.js +0 -9
|
@@ -3,15 +3,17 @@ import { subscribeSpyTo } from "@hirez_io/observer-spy";
|
|
|
3
3
|
import { getSeenRelays } from "applesauce-core/helpers";
|
|
4
4
|
import { WS } from "vitest-websocket-mock";
|
|
5
5
|
import { Relay } from "../relay.js";
|
|
6
|
+
import { filter } from "rxjs/operators";
|
|
7
|
+
import { firstValueFrom } from "rxjs";
|
|
6
8
|
let mockRelay;
|
|
7
9
|
let relay;
|
|
8
10
|
beforeEach(async () => {
|
|
9
|
-
mockRelay = new WS("wss://test");
|
|
11
|
+
mockRelay = new WS("wss://test", { jsonProtocol: true });
|
|
10
12
|
relay = new Relay("wss://test");
|
|
13
|
+
relay.keepAlive = 0;
|
|
11
14
|
});
|
|
15
|
+
// Wait for server to close to prevent memory leaks
|
|
12
16
|
afterEach(async () => {
|
|
13
|
-
mockRelay.close();
|
|
14
|
-
// Wait for server to close to prevent memory leaks
|
|
15
17
|
await WS.clean();
|
|
16
18
|
});
|
|
17
19
|
const mockEvent = {
|
|
@@ -24,44 +26,48 @@ const mockEvent = {
|
|
|
24
26
|
sig: "5a57b5a12bba4b7cf0121077b1421cf4df402c5c221376c076204fc4f7519e28ce6508f26ddc132c406ccfe6e62cc6db857b96c788565cdca9674fe9a0710ac2",
|
|
25
27
|
};
|
|
26
28
|
describe("req", () => {
|
|
29
|
+
it("should trigger connection to relay", async () => {
|
|
30
|
+
subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
|
|
31
|
+
// Wait for connection
|
|
32
|
+
await firstValueFrom(relay.connected$.pipe(filter(Boolean)));
|
|
33
|
+
expect(relay.connected).toBe(true);
|
|
34
|
+
});
|
|
27
35
|
it("should send REQ and CLOSE messages", async () => {
|
|
28
36
|
// Create subscription that completes after first EOSE
|
|
29
37
|
const sub = relay.req([{ kinds: [1] }], "sub1").subscribe();
|
|
30
38
|
// Verify REQ was sent
|
|
31
|
-
|
|
32
|
-
expect(JSON.parse(reqMessage)).toEqual(["REQ", "sub1", { kinds: [1] }]);
|
|
39
|
+
expect(await mockRelay.nextMessage).toEqual(["REQ", "sub1", { kinds: [1] }]);
|
|
33
40
|
// Send EOSE to complete subscription
|
|
34
|
-
mockRelay.send(
|
|
41
|
+
mockRelay.send(["EOSE", "sub1"]);
|
|
35
42
|
// Complete the subscription
|
|
36
43
|
sub.unsubscribe();
|
|
37
44
|
// Verify CLOSE was sent
|
|
38
|
-
|
|
39
|
-
expect(JSON.parse(closeMessage)).toEqual(["CLOSE", "sub1"]);
|
|
45
|
+
expect(await mockRelay.nextMessage).toEqual(["CLOSE", "sub1"]);
|
|
40
46
|
});
|
|
41
47
|
it("should emit nostr event and EOSE", async () => {
|
|
42
48
|
const spy = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
|
|
43
49
|
// Send EVENT message
|
|
44
|
-
mockRelay.send(
|
|
50
|
+
mockRelay.send(["EVENT", "sub1", mockEvent]);
|
|
45
51
|
// Send EOSE message
|
|
46
|
-
mockRelay.send(
|
|
52
|
+
mockRelay.send(["EOSE", "sub1"]);
|
|
47
53
|
expect(spy.getValues()).toEqual([expect.objectContaining(mockEvent), "EOSE"]);
|
|
48
54
|
});
|
|
49
55
|
it("should ignore EVENT and EOSE messages that do not match subscription id", async () => {
|
|
50
56
|
const spy = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
|
|
51
57
|
// Send EVENT message with wrong subscription id
|
|
52
|
-
mockRelay.send(
|
|
58
|
+
mockRelay.send(["EVENT", "wrong_sub", mockEvent]);
|
|
53
59
|
// Send EOSE message with wrong subscription id
|
|
54
|
-
mockRelay.send(
|
|
60
|
+
mockRelay.send(["EOSE", "wrong_sub"]);
|
|
55
61
|
// Send EVENT message with correct subscription id
|
|
56
|
-
mockRelay.send(
|
|
62
|
+
mockRelay.send(["EVENT", "sub1", mockEvent]);
|
|
57
63
|
// Send EOSE message with correct subscription id
|
|
58
|
-
mockRelay.send(
|
|
64
|
+
mockRelay.send(["EOSE", "sub1"]);
|
|
59
65
|
expect(spy.getValues()).toEqual([expect.objectContaining(mockEvent), "EOSE"]);
|
|
60
66
|
});
|
|
61
67
|
it("should mark events with their source relay", async () => {
|
|
62
68
|
const spy = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
|
|
63
69
|
// Send EVENT message
|
|
64
|
-
mockRelay.send(
|
|
70
|
+
mockRelay.send(["EVENT", "sub1", mockEvent]);
|
|
65
71
|
// Get the received event
|
|
66
72
|
const receivedEvent = spy.getValues()[0];
|
|
67
73
|
// Verify the event was marked as seen from this relay
|
|
@@ -70,35 +76,39 @@ describe("req", () => {
|
|
|
70
76
|
it("should complete subscription when CLOSED message is received", async () => {
|
|
71
77
|
const spy = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
|
|
72
78
|
// Send CLOSED message for the subscription
|
|
73
|
-
mockRelay.send(
|
|
79
|
+
mockRelay.send(["CLOSED", "sub1", "reason"]);
|
|
74
80
|
// Verify the subscription completed
|
|
75
81
|
expect(spy.receivedComplete()).toBe(true);
|
|
76
82
|
});
|
|
77
83
|
});
|
|
78
84
|
describe("event", () => {
|
|
85
|
+
it("should trigger connection to relay", async () => {
|
|
86
|
+
subscribeSpyTo(relay.event(mockEvent));
|
|
87
|
+
// Wait for connection
|
|
88
|
+
await firstValueFrom(relay.connected$.pipe(filter(Boolean)));
|
|
89
|
+
expect(relay.connected).toBe(true);
|
|
90
|
+
});
|
|
79
91
|
it("observable should complete when matching OK response received", async () => {
|
|
80
92
|
const spy = subscribeSpyTo(relay.event(mockEvent));
|
|
81
93
|
// Verify EVENT message was sent
|
|
82
|
-
|
|
83
|
-
expect(JSON.parse(eventMessage)).toEqual(["EVENT", mockEvent]);
|
|
94
|
+
expect(await mockRelay.nextMessage).toEqual(["EVENT", mockEvent]);
|
|
84
95
|
// Send matching OK response
|
|
85
|
-
mockRelay.send(
|
|
96
|
+
mockRelay.send(["OK", mockEvent.id, true, ""]);
|
|
86
97
|
await spy.onComplete();
|
|
87
98
|
expect(spy.receivedComplete()).toBe(true);
|
|
88
99
|
});
|
|
89
100
|
it("should ignore OK responses for different events", async () => {
|
|
90
101
|
const spy = subscribeSpyTo(relay.event(mockEvent));
|
|
91
102
|
// Send non-matching OK response
|
|
92
|
-
mockRelay.send(
|
|
103
|
+
mockRelay.send(["OK", "different_id", true, ""]);
|
|
93
104
|
expect(spy.receivedComplete()).toBe(false);
|
|
94
105
|
// Send matching OK response
|
|
95
|
-
mockRelay.send(
|
|
106
|
+
mockRelay.send(["OK", mockEvent.id, true, ""]);
|
|
96
107
|
expect(spy.receivedComplete()).toBe(true);
|
|
97
108
|
});
|
|
98
109
|
it("should send EVENT message to relay", async () => {
|
|
99
110
|
relay.event(mockEvent).subscribe();
|
|
100
|
-
|
|
101
|
-
expect(JSON.parse(eventMessage)).toEqual(["EVENT", mockEvent]);
|
|
111
|
+
expect(await mockRelay.nextMessage).toEqual(["EVENT", mockEvent]);
|
|
102
112
|
});
|
|
103
113
|
it("should complete with error if no OK received within 10s", async () => {
|
|
104
114
|
vi.useFakeTimers();
|
|
@@ -109,37 +119,65 @@ describe("event", () => {
|
|
|
109
119
|
expect(spy.getLastValue()).toEqual({ ok: false, from: "wss://test", message: "Timeout" });
|
|
110
120
|
});
|
|
111
121
|
});
|
|
112
|
-
describe("
|
|
122
|
+
describe("notices$", () => {
|
|
123
|
+
it("should not trigger connection to relay", async () => {
|
|
124
|
+
subscribeSpyTo(relay.notices$);
|
|
125
|
+
expect(relay.connected).toBe(false);
|
|
126
|
+
});
|
|
113
127
|
it("should accumulate notices in notices$ state", async () => {
|
|
114
128
|
subscribeSpyTo(relay.req({ kinds: [1] }));
|
|
115
129
|
// Send multiple NOTICE messages
|
|
116
|
-
mockRelay.send(
|
|
117
|
-
mockRelay.send(
|
|
118
|
-
mockRelay.send(
|
|
130
|
+
mockRelay.send(["NOTICE", "Notice 1"]);
|
|
131
|
+
mockRelay.send(["NOTICE", "Notice 2"]);
|
|
132
|
+
mockRelay.send(["NOTICE", "Notice 3"]);
|
|
119
133
|
// Verify the notices state contains all messages
|
|
120
134
|
expect(relay.notices$.value).toEqual(["Notice 1", "Notice 2", "Notice 3"]);
|
|
121
135
|
});
|
|
122
136
|
it("should ignore non-NOTICE messages", async () => {
|
|
123
137
|
subscribeSpyTo(relay.req({ kinds: [1] }));
|
|
124
|
-
mockRelay.send(
|
|
125
|
-
mockRelay.send(
|
|
138
|
+
mockRelay.send(["NOTICE", "Important notice"]);
|
|
139
|
+
mockRelay.send(["OTHER", "other message"]);
|
|
126
140
|
// Verify only NOTICE messages are in the state
|
|
127
141
|
expect(relay.notices$.value).toEqual(["Important notice"]);
|
|
128
142
|
});
|
|
129
143
|
});
|
|
130
144
|
describe("challenge$", () => {
|
|
145
|
+
it("should not trigger connection to relay", async () => {
|
|
146
|
+
subscribeSpyTo(relay.challenge$);
|
|
147
|
+
expect(relay.connected).toBe(false);
|
|
148
|
+
});
|
|
131
149
|
it("should set challenge$ when AUTH message received", async () => {
|
|
132
150
|
subscribeSpyTo(relay.req({ kinds: [1] }));
|
|
133
151
|
// Send AUTH message with challenge string
|
|
134
|
-
mockRelay.send(
|
|
152
|
+
mockRelay.send(["AUTH", "challenge-string-123"]);
|
|
135
153
|
// Verify challenge$ was set
|
|
136
154
|
expect(relay.challenge$.value).toBe("challenge-string-123");
|
|
137
155
|
});
|
|
138
156
|
it("should ignore non-AUTH messages", async () => {
|
|
139
157
|
subscribeSpyTo(relay.req({ kinds: [1] }));
|
|
140
|
-
mockRelay.send(
|
|
141
|
-
mockRelay.send(
|
|
158
|
+
mockRelay.send(["NOTICE", "Not a challenge"]);
|
|
159
|
+
mockRelay.send(["OTHER", "other message"]);
|
|
142
160
|
// Verify challenge$ remains null
|
|
143
161
|
expect(relay.challenge$.value).toBe(null);
|
|
144
162
|
});
|
|
145
163
|
});
|
|
164
|
+
// describe("keepAlive", () => {
|
|
165
|
+
// it("should close the socket connection after keepAlive timeout", async () => {
|
|
166
|
+
// vi.useFakeTimers();
|
|
167
|
+
// // Set a short keepAlive timeout for testing
|
|
168
|
+
// relay.keepAlive = 100; // 100ms for quick testing
|
|
169
|
+
// // Subscribe to the relay to ensure it is active
|
|
170
|
+
// const sub = subscribeSpyTo(relay.req([{ kinds: [1] }]));
|
|
171
|
+
// // Wait for connection
|
|
172
|
+
// await firstValueFrom(relay.connected$.pipe(filter(Boolean)));
|
|
173
|
+
// // Close the subscription
|
|
174
|
+
// sub.unsubscribe();
|
|
175
|
+
// // Fast-forward time by 10ms
|
|
176
|
+
// await vi.advanceTimersByTimeAsync(10);
|
|
177
|
+
// // should still be connected
|
|
178
|
+
// expect(relay.connected).toBe(true);
|
|
179
|
+
// // Wait for the keepAlive timeout to elapse
|
|
180
|
+
// await vi.advanceTimersByTimeAsync(150);
|
|
181
|
+
// expect(relay.connected).toBe(false);
|
|
182
|
+
// });
|
|
183
|
+
// });
|
package/dist/relay.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { logger } from "applesauce-core";
|
|
2
|
+
import { type Filter, type NostrEvent } from "nostr-tools";
|
|
1
3
|
import { BehaviorSubject, Observable } from "rxjs";
|
|
2
4
|
import { WebSocketSubject, WebSocketSubjectConfig } from "rxjs/webSocket";
|
|
3
|
-
import { type Filter, type NostrEvent } from "nostr-tools";
|
|
4
|
-
import { logger } from "applesauce-core";
|
|
5
5
|
import { IRelay, PublishResponse, SubscriptionResponse } from "./types.js";
|
|
6
6
|
export type RelayOptions = {
|
|
7
7
|
WebSocket?: WebSocketSubjectConfig<any>["WebSocketCtor"];
|
|
@@ -26,6 +26,8 @@ export declare class Relay implements IRelay {
|
|
|
26
26
|
eoseTimeout: number;
|
|
27
27
|
/** How long to wait for an OK message from the relay */
|
|
28
28
|
eventTimeout: number;
|
|
29
|
+
/** How long to keep the connection alive after nothing is subscribed */
|
|
30
|
+
keepAlive: number;
|
|
29
31
|
protected authRequiredForReq: BehaviorSubject<boolean>;
|
|
30
32
|
protected authRequiredForPublish: BehaviorSubject<boolean>;
|
|
31
33
|
protected resetState(): void;
|
package/dist/relay.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BehaviorSubject, combineLatest, defer, filter, ignoreElements, map, merge, NEVER, of, scan, share, switchMap, take, takeWhile, tap, timeout, } from "rxjs";
|
|
2
|
-
import { webSocket } from "rxjs/webSocket";
|
|
3
|
-
import { nanoid } from "nanoid";
|
|
4
1
|
import { logger } from "applesauce-core";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
import { BehaviorSubject, combineLatest, defer, filter, ignoreElements, map, merge, NEVER, of, scan, share, switchMap, take, takeWhile, tap, timeout, timer } from "rxjs";
|
|
4
|
+
import { webSocket } from "rxjs/webSocket";
|
|
5
5
|
import { markFromRelay } from "./operators/mark-from-relay.js";
|
|
6
6
|
export class Relay {
|
|
7
7
|
url;
|
|
@@ -32,6 +32,8 @@ export class Relay {
|
|
|
32
32
|
eoseTimeout = 10_000;
|
|
33
33
|
/** How long to wait for an OK message from the relay */
|
|
34
34
|
eventTimeout = 10_000;
|
|
35
|
+
/** How long to keep the connection alive after nothing is subscribed */
|
|
36
|
+
keepAlive = 30_000;
|
|
35
37
|
authRequiredForReq = new BehaviorSubject(false);
|
|
36
38
|
authRequiredForPublish = new BehaviorSubject(false);
|
|
37
39
|
resetState() {
|
|
@@ -98,7 +100,7 @@ export class Relay {
|
|
|
98
100
|
// Never emit any values
|
|
99
101
|
ignoreElements(),
|
|
100
102
|
// There should only be a single watch tower
|
|
101
|
-
share());
|
|
103
|
+
share({ resetOnRefCountZero: () => timer(this.keepAlive) }));
|
|
102
104
|
}
|
|
103
105
|
waitForAuth(
|
|
104
106
|
// NOTE: require BehaviorSubject so it always has a value
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-relay",
|
|
3
|
-
"version": "0.0.0-next-
|
|
3
|
+
"version": "0.0.0-next-20250423145737",
|
|
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": "0.0.0-next-
|
|
57
|
+
"applesauce-core": "0.0.0-next-20250423145737",
|
|
58
58
|
"nanoid": "^5.0.9",
|
|
59
59
|
"nostr-tools": "^2.10.4",
|
|
60
60
|
"rxjs": "^7.8.1"
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import * as Nostr from "nostr-typedef";
|
|
2
|
-
export declare const faker: {
|
|
3
|
-
filter(filter?: Nostr.Filter): Nostr.Filter;
|
|
4
|
-
filters(): Nostr.Filter[];
|
|
5
|
-
event<const K extends number>(event?: Partial<Nostr.Event<K>>): Nostr.Event<K>;
|
|
6
|
-
AUTH(event?: Partial<Nostr.Event<Nostr.Kind.ClientAuthentication>>): Nostr.ToRelayMessage.AUTH;
|
|
7
|
-
CLOSE(subId: string): Nostr.ToRelayMessage.CLOSE;
|
|
8
|
-
COUNT(subId: string, filters?: Nostr.Filter[]): Nostr.ToRelayMessage.COUNT;
|
|
9
|
-
EVENT(event?: Partial<Nostr.Event>): Nostr.ToRelayMessage.EVENT;
|
|
10
|
-
REQ(subId: string, filters?: Nostr.Filter[]): Nostr.ToRelayMessage.REQ;
|
|
11
|
-
toClientMessage: {
|
|
12
|
-
AUTH(message?: string): Nostr.ToClientMessage.AUTH;
|
|
13
|
-
COUNT(subId: string, count?: number): Nostr.ToClientMessage.COUNT;
|
|
14
|
-
EOSE(subId: string): Nostr.ToClientMessage.EOSE;
|
|
15
|
-
EVENT(subId: string, event?: Partial<Nostr.Event>): Nostr.ToClientMessage.EVENT;
|
|
16
|
-
NOTICE(message?: string): Nostr.ToClientMessage.NOTICE;
|
|
17
|
-
OK(eventId: string, succeeded: boolean, message?: string): Nostr.ToClientMessage.OK;
|
|
18
|
-
};
|
|
19
|
-
};
|
package/dist/__tests__/faker.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { kinds } from "nostr-tools";
|
|
2
|
-
export const faker = {
|
|
3
|
-
filter(filter) {
|
|
4
|
-
return filter ?? { kinds: [0] };
|
|
5
|
-
},
|
|
6
|
-
filters() {
|
|
7
|
-
return [faker.filter()];
|
|
8
|
-
},
|
|
9
|
-
event(event) {
|
|
10
|
-
return {
|
|
11
|
-
id: "*",
|
|
12
|
-
content: "*",
|
|
13
|
-
created_at: 0,
|
|
14
|
-
kind: 0,
|
|
15
|
-
pubkey: "*",
|
|
16
|
-
sig: "*",
|
|
17
|
-
tags: [],
|
|
18
|
-
...event,
|
|
19
|
-
};
|
|
20
|
-
},
|
|
21
|
-
AUTH(event) {
|
|
22
|
-
return ["AUTH", { ...faker.event(event), kind: kinds.ClientAuth }];
|
|
23
|
-
},
|
|
24
|
-
CLOSE(subId) {
|
|
25
|
-
return ["CLOSE", subId];
|
|
26
|
-
},
|
|
27
|
-
COUNT(subId, filters) {
|
|
28
|
-
return ["COUNT", subId, ...(filters ?? faker.filters())];
|
|
29
|
-
},
|
|
30
|
-
EVENT(event) {
|
|
31
|
-
return ["EVENT", faker.event(event)];
|
|
32
|
-
},
|
|
33
|
-
REQ(subId, filters) {
|
|
34
|
-
return ["REQ", subId, ...(filters ?? faker.filters())];
|
|
35
|
-
},
|
|
36
|
-
toClientMessage: {
|
|
37
|
-
AUTH(message) {
|
|
38
|
-
return ["AUTH", message ?? "*"];
|
|
39
|
-
},
|
|
40
|
-
COUNT(subId, count) {
|
|
41
|
-
return ["COUNT", subId, { count: count ?? 0 }];
|
|
42
|
-
},
|
|
43
|
-
EOSE(subId) {
|
|
44
|
-
return ["EOSE", subId];
|
|
45
|
-
},
|
|
46
|
-
EVENT(subId, event) {
|
|
47
|
-
return ["EVENT", subId, faker.event(event)];
|
|
48
|
-
},
|
|
49
|
-
NOTICE(message) {
|
|
50
|
-
return ["NOTICE", message ?? "*"];
|
|
51
|
-
},
|
|
52
|
-
OK(eventId, succeeded, message) {
|
|
53
|
-
return ["OK", eventId, succeeded, message ?? "*"];
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
};
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import Nostr from "nostr-typedef";
|
|
2
|
-
declare module "vitest" {
|
|
3
|
-
interface Assertion<T = any> extends CustomMatchers {
|
|
4
|
-
}
|
|
5
|
-
interface AsymmetricMatchersContaining extends CustomMatchers {
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
interface CustomMatchers {
|
|
9
|
-
beToRelayAUTH(): void;
|
|
10
|
-
beToRelayAUTH(event: Partial<Nostr.Event<Nostr.Kind.ClientAuthentication>>): void;
|
|
11
|
-
beToRelayCLOSE(): void;
|
|
12
|
-
beToRelayCLOSE(subId: string): void;
|
|
13
|
-
beToRelayCOUNT(): void;
|
|
14
|
-
beToRelayCOUNT(subId: string): void;
|
|
15
|
-
beToRelayCOUNT(expected: [subId: string, ...filters: Nostr.Filter[]]): void;
|
|
16
|
-
beToRelayEVENT(): void;
|
|
17
|
-
beToRelayEVENT(event: Partial<Nostr.Event>): void;
|
|
18
|
-
beToRelayREQ(): void;
|
|
19
|
-
beToRelayREQ(subId: string): void;
|
|
20
|
-
beToRelayREQ(expected: [subId: string, ...filters: Nostr.Filter[]]): void;
|
|
21
|
-
beToClientAUTH(): void;
|
|
22
|
-
beToClientAUTH(challengeMessage: string): void;
|
|
23
|
-
beToClientCOUNT(): void;
|
|
24
|
-
beToClientCOUNT(subId: string): void;
|
|
25
|
-
beToClientCOUNT(expected: [subId: string, count: number]): void;
|
|
26
|
-
beToClientEOSE(): void;
|
|
27
|
-
beToClientEOSE(subId: string): void;
|
|
28
|
-
beToClientEVENT(): void;
|
|
29
|
-
beToClientEVENT(subId: string): void;
|
|
30
|
-
beToClientEVENT(expected: [subId: string, event: Partial<Nostr.Event>]): void;
|
|
31
|
-
beToClientNOTICE(): void;
|
|
32
|
-
beToClientNOTICE(message: string): void;
|
|
33
|
-
beToClientOK(expected: [eventId: string, succeeded: boolean, message?: string]): void;
|
|
34
|
-
toReceiveAUTH(): Promise<void>;
|
|
35
|
-
toReceiveAUTH(event: Partial<Nostr.Event<Nostr.Kind.ClientAuthentication>>): Promise<void>;
|
|
36
|
-
toReceiveCLOSE(): Promise<void>;
|
|
37
|
-
toReceiveCLOSE(subId: string): Promise<void>;
|
|
38
|
-
toReceiveCOUNT(): Promise<void>;
|
|
39
|
-
toReceiveCOUNT(subId: string): Promise<void>;
|
|
40
|
-
toReceiveCOUNT(expected: [subId: string, ...filters: Nostr.Filter[]]): Promise<void>;
|
|
41
|
-
toReceiveEVENT(): Promise<void>;
|
|
42
|
-
toReceiveEVENT(event: Partial<Nostr.Event>): Promise<void>;
|
|
43
|
-
toReceiveREQ(): Promise<void>;
|
|
44
|
-
toReceiveREQ(subId: string): Promise<void>;
|
|
45
|
-
toReceiveREQ(expected: [subId: string, ...filters: Nostr.Filter[]]): Promise<void>;
|
|
46
|
-
toSeeAUTH(): Promise<void>;
|
|
47
|
-
toSeeAUTH(challengeMessage: string): Promise<void>;
|
|
48
|
-
toSeeCOUNT(): Promise<void>;
|
|
49
|
-
toSeeCOUNT(subId: string): Promise<void>;
|
|
50
|
-
toSeeCOUNT(expected: [subId: string, count: number]): Promise<void>;
|
|
51
|
-
toSeeEOSE(): Promise<void>;
|
|
52
|
-
toSeeEOSE(subId: string): Promise<void>;
|
|
53
|
-
toSeeEVENT(): Promise<void>;
|
|
54
|
-
toSeeEVENT(subId: string): Promise<void>;
|
|
55
|
-
toSeeEVENT(expected: [subId: string, event: Partial<Nostr.Event>]): Promise<void>;
|
|
56
|
-
toSeeNOTICE(): Promise<void>;
|
|
57
|
-
toSeeNOTICE(message: string): Promise<void>;
|
|
58
|
-
toSeeOK(expected: [eventId: string, succeeded: boolean, message?: string]): Promise<void>;
|
|
59
|
-
}
|
|
60
|
-
export {};
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { expect } from "vitest";
|
|
2
|
-
import { deriveToReceiveMessage } from "vitest-websocket-mock";
|
|
3
|
-
import { TimeoutError, withTimeout } from "./utils.js";
|
|
4
|
-
const beToRelayAUTH = function (actual, event) {
|
|
5
|
-
const pass = isNostrMessage("AUTH", actual) && (event === undefined || matchEvent(actual[1], event, this.equals.bind(this)));
|
|
6
|
-
return {
|
|
7
|
-
message: message("a to-relay-AUTH message", ["AUTH", event ?? "*"], this, actual, pass),
|
|
8
|
-
pass,
|
|
9
|
-
};
|
|
10
|
-
};
|
|
11
|
-
const beToRelayCLOSE = function (actual, subId) {
|
|
12
|
-
const pass = isNostrMessage("CLOSE", actual) && (subId === undefined || actual[1] === subId);
|
|
13
|
-
return {
|
|
14
|
-
message: message("a to-relay-CLOSE message", ["CLOSE", subId ?? "*"], this, actual, pass),
|
|
15
|
-
pass,
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
const beToRelayCOUNT = function (actual, pred) {
|
|
19
|
-
const subId = typeof pred === "string" ? pred : pred !== undefined ? pred[0] : null;
|
|
20
|
-
const filters = typeof pred === "object" && pred !== undefined ? pred.slice(1) : null;
|
|
21
|
-
const pass = isNostrMessage("COUNT", actual) &&
|
|
22
|
-
(subId === null || actual[1] === subId) &&
|
|
23
|
-
(filters === null || this.equals(actual.slice(2), filters));
|
|
24
|
-
return {
|
|
25
|
-
message: message("a to-relay-COUNT message", ["COUNT", subId ?? "*", ...(filters === null ? ["*"] : filters)], this, actual, pass),
|
|
26
|
-
pass,
|
|
27
|
-
};
|
|
28
|
-
};
|
|
29
|
-
const beToRelayEVENT = function (actual, event) {
|
|
30
|
-
const pass = isNostrMessage("EVENT", actual) && (event === undefined || matchEvent(actual[1], event, this.equals.bind(this)));
|
|
31
|
-
return {
|
|
32
|
-
message: message("a to-relay-EVENT message", ["EVENT", event ?? "*"], this, actual, pass),
|
|
33
|
-
pass,
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
const beToRelayREQ = function (actual, pred) {
|
|
37
|
-
const subId = typeof pred === "string" ? pred : pred !== undefined ? pred[0] : null;
|
|
38
|
-
const filters = typeof pred === "object" && pred !== undefined ? pred.slice(1) : null;
|
|
39
|
-
const pass = isNostrMessage("REQ", actual) &&
|
|
40
|
-
(subId === null || actual[1] === subId) &&
|
|
41
|
-
(filters === null || this.equals(actual.slice(2), filters));
|
|
42
|
-
return {
|
|
43
|
-
message: message("a to-relay-REQ message", ["REQ", subId ?? "*", ...(filters === null ? ["*"] : filters)], this, actual, pass),
|
|
44
|
-
pass,
|
|
45
|
-
};
|
|
46
|
-
};
|
|
47
|
-
const beToClientAUTH = function (actual, challengeMessage) {
|
|
48
|
-
const pass = isNostrMessage("AUTH", actual) && (challengeMessage === undefined || actual[1] === challengeMessage);
|
|
49
|
-
return {
|
|
50
|
-
message: message("a to-client-AUTH message", ["AUTH", challengeMessage ?? "*"], this, actual, pass),
|
|
51
|
-
pass,
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
const beToClientCOUNT = function (actual, pred) {
|
|
55
|
-
const subId = typeof pred === "string" ? pred : pred !== undefined ? pred[0] : null;
|
|
56
|
-
const count = typeof pred === "object" && pred !== undefined ? pred[1] : null;
|
|
57
|
-
const pass = isNostrMessage("COUNT", actual) &&
|
|
58
|
-
(subId === null || actual[1] === subId) &&
|
|
59
|
-
(count === null || this.equals(actual[2], { count }));
|
|
60
|
-
return {
|
|
61
|
-
message: message("a to-client-COUNT message", ["COUNT", subId ?? "*", count === null ? "*" : { count }], this, actual, pass),
|
|
62
|
-
pass,
|
|
63
|
-
};
|
|
64
|
-
};
|
|
65
|
-
const beToClientEOSE = function (actual, subId) {
|
|
66
|
-
const pass = isNostrMessage("EOSE", actual) && (subId === undefined || actual[1] === subId);
|
|
67
|
-
return {
|
|
68
|
-
message: message("a to-client-EOSE message", ["COUNT", subId ?? "*"], this, actual, pass),
|
|
69
|
-
pass,
|
|
70
|
-
};
|
|
71
|
-
};
|
|
72
|
-
const beToClientEVENT = function (actual, pred) {
|
|
73
|
-
const subId = typeof pred === "string" ? pred : pred !== undefined ? pred[0] : null;
|
|
74
|
-
const event = typeof pred === "object" && pred !== undefined ? pred[1] : null;
|
|
75
|
-
const pass = isNostrMessage("EVENT", actual) &&
|
|
76
|
-
(subId === null || actual[1] === subId) &&
|
|
77
|
-
(event === null || matchEvent(actual[2], event, this.equals.bind(this)));
|
|
78
|
-
return {
|
|
79
|
-
message: message("a to-client-EVENT message", ["EVENT", subId ?? "*", event ?? "*"], this, actual, pass),
|
|
80
|
-
pass,
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
const beToClientNOTICE = function (actual, noticeMessage) {
|
|
84
|
-
const pass = isNostrMessage("NOTICE", actual) && (noticeMessage === undefined || actual[1] === noticeMessage);
|
|
85
|
-
return {
|
|
86
|
-
message: message("a to-client-NOTICE message", ["EVENT", noticeMessage ?? "*"], this, actual, pass),
|
|
87
|
-
pass,
|
|
88
|
-
};
|
|
89
|
-
};
|
|
90
|
-
const beToClientOK = function (actual, expected) {
|
|
91
|
-
const pass = isNostrMessage("OK", actual) &&
|
|
92
|
-
(expected === undefined ||
|
|
93
|
-
(actual[1] === expected[0] &&
|
|
94
|
-
actual[2] === expected[1] &&
|
|
95
|
-
(expected[2] === undefined || actual[3] === expected[2])));
|
|
96
|
-
return {
|
|
97
|
-
message: message("a to-client-OK message", ["OK", ...(expected ?? ["*", "*", "*"])], this, actual, pass),
|
|
98
|
-
pass,
|
|
99
|
-
};
|
|
100
|
-
};
|
|
101
|
-
const toReceiveAUTH = deriveToReceiveMessage("toReceiveAUTH", beToRelayAUTH);
|
|
102
|
-
const toReceiveCLOSE = deriveToReceiveMessage("toReceiveCLOSE", beToRelayCLOSE);
|
|
103
|
-
const toReceiveCOUNT = deriveToReceiveMessage("toReceiveCOUNT", beToRelayCOUNT);
|
|
104
|
-
const toReceiveEVENT = deriveToReceiveMessage("toReceiveEVENT", beToRelayEVENT);
|
|
105
|
-
const toReceiveREQ = deriveToReceiveMessage("toReceiveREQ", beToRelayREQ);
|
|
106
|
-
const toSeeAUTH = clientMathcer("toSeeAUTH", beToClientAUTH);
|
|
107
|
-
const toSeeCOUNT = clientMathcer("toSeeCOUNT", beToClientCOUNT);
|
|
108
|
-
const toSeeEOSE = clientMathcer("toSeeEOSE", beToClientEOSE);
|
|
109
|
-
const toSeeEVENT = clientMathcer("toSeeEVENT", beToClientEVENT);
|
|
110
|
-
const toSeeNOTICE = clientMathcer("toSeeNOTICE", beToClientNOTICE);
|
|
111
|
-
const toSeeOK = clientMathcer("toSeeOK", beToClientOK);
|
|
112
|
-
expect.extend({
|
|
113
|
-
beToRelayAUTH,
|
|
114
|
-
beToRelayCLOSE,
|
|
115
|
-
beToRelayCOUNT,
|
|
116
|
-
beToRelayEVENT,
|
|
117
|
-
beToRelayREQ,
|
|
118
|
-
beToClientAUTH,
|
|
119
|
-
beToClientCOUNT,
|
|
120
|
-
beToClientEOSE,
|
|
121
|
-
beToClientEVENT,
|
|
122
|
-
beToClientNOTICE,
|
|
123
|
-
beToClientOK,
|
|
124
|
-
toReceiveAUTH,
|
|
125
|
-
toReceiveCLOSE,
|
|
126
|
-
toReceiveCOUNT,
|
|
127
|
-
toReceiveEVENT,
|
|
128
|
-
toReceiveREQ,
|
|
129
|
-
toSeeAUTH,
|
|
130
|
-
toSeeCOUNT,
|
|
131
|
-
toSeeEOSE,
|
|
132
|
-
toSeeEVENT,
|
|
133
|
-
toSeeNOTICE,
|
|
134
|
-
toSeeOK,
|
|
135
|
-
});
|
|
136
|
-
function clientMathcer(name, fn) {
|
|
137
|
-
return async function (spy, pred, options) {
|
|
138
|
-
if (spy.__createClientSpy) {
|
|
139
|
-
try {
|
|
140
|
-
return await withTimeout(async () => fn.call(this, await spy.next(), pred, options));
|
|
141
|
-
}
|
|
142
|
-
catch (err) {
|
|
143
|
-
if (err instanceof TimeoutError) {
|
|
144
|
-
return {
|
|
145
|
-
pass: this.isNot,
|
|
146
|
-
message: () => this.utils.matcherHint(`${this.isNot ? ".not" : ""}.${name}`, "ClientSpy", "expected") +
|
|
147
|
-
"\n\n" +
|
|
148
|
-
`Client spy was waiting for the next message from the relay, but timed out.`,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
throw err;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
return {
|
|
158
|
-
pass: this.isNot,
|
|
159
|
-
message: () => this.utils.matcherHint(`${this.isNot ? ".not" : ""}.${name}`, "ClientSpy", "expected") +
|
|
160
|
-
"\n\n" +
|
|
161
|
-
`Mathcer \`${name}\` must be used for ClientSpy, but now being used for:\n\n` +
|
|
162
|
-
`${this.utils.printReceived(spy)}\n`,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
function isNostrMessage(type, message) {
|
|
168
|
-
return Array.isArray(message) && message[0] === type;
|
|
169
|
-
}
|
|
170
|
-
function matchEvent(received, expected, equal) {
|
|
171
|
-
const eventKeys = ["id", "sig", "kind", "tags", "pubkey", "content", "created_at"];
|
|
172
|
-
const isEvent = (x) => typeof x === "object" && x !== null && eventKeys.every((key) => key in x);
|
|
173
|
-
return (isEvent(received) && eventKeys.every((key) => expected[key] === undefined || equal(received[key], expected[key])));
|
|
174
|
-
}
|
|
175
|
-
function message(entityName, expected, state, actual, pass) {
|
|
176
|
-
return () => `It was expected ${pass ? "not " : ""}to be ${entityName}, like this:\n\n${state.utils.printExpected(expected)}\n\nbut got:\n\n${state.utils.printReceived(actual)}\n`;
|
|
177
|
-
}
|
package/dist/__tests__/mock.d.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { CloseOptions } from "mock-socket";
|
|
2
|
-
import Nostr from "nostr-typedef";
|
|
3
|
-
import { WS } from "vitest-websocket-mock";
|
|
4
|
-
export interface MockServerSocket {
|
|
5
|
-
id: number;
|
|
6
|
-
send(message: unknown): void;
|
|
7
|
-
close(options?: CloseOptions): void;
|
|
8
|
-
}
|
|
9
|
-
export interface MockServerBehavior {
|
|
10
|
-
onOpen?(socket: MockServerSocket): void;
|
|
11
|
-
onMessage?(socket: MockServerSocket, message: string): void;
|
|
12
|
-
onClose?(socket: MockServerSocket): void;
|
|
13
|
-
onError?(socket: MockServerSocket, error: Error): void;
|
|
14
|
-
}
|
|
15
|
-
interface MockServer extends WS {
|
|
16
|
-
getSockets: (count: number, options?: {
|
|
17
|
-
timeout?: number;
|
|
18
|
-
}) => Promise<MockServerSocket[]>;
|
|
19
|
-
getSocket: (index: number) => Promise<MockServerSocket>;
|
|
20
|
-
getSocketsSync: () => MockServerSocket[];
|
|
21
|
-
}
|
|
22
|
-
export declare function createMockServer(url: string, behavior: MockServerBehavior): MockServer;
|
|
23
|
-
export interface MockRelay extends MockServer {
|
|
24
|
-
/**
|
|
25
|
-
* Pop the latest message that has not yet been consumed.
|
|
26
|
-
* If such a message does not yet exist, it waits for the message
|
|
27
|
-
* until it times out.
|
|
28
|
-
*
|
|
29
|
-
* The default timeout is 1000 miliseconds.
|
|
30
|
-
*/
|
|
31
|
-
next: (timeout?: number) => Promise<Nostr.ToRelayMessage.Any>;
|
|
32
|
-
/**
|
|
33
|
-
* Pop the latest `count` messages that have not yet been consumed.
|
|
34
|
-
* If such a message does not yet exist, it waits for the message
|
|
35
|
-
* until it times out.
|
|
36
|
-
*
|
|
37
|
-
* The default timeout is 1000 miliseconds.
|
|
38
|
-
*/
|
|
39
|
-
nexts: (count: number, timeout?: number) => Promise<Nostr.ToRelayMessage.Any[]>;
|
|
40
|
-
/**
|
|
41
|
-
* Send messages of any format from the given socket mock.
|
|
42
|
-
* If `socket` is omitted, it will be sent from all active socket mocks.
|
|
43
|
-
*
|
|
44
|
-
* If the message is a string, as-is string will be sent,
|
|
45
|
-
* otherwise JSON strigify will be attempted.
|
|
46
|
-
*
|
|
47
|
-
* Note that the messages sent by this method are outside
|
|
48
|
-
* the management of the mock relay.
|
|
49
|
-
* This means that if, for example, you use this method to send an OK message,
|
|
50
|
-
* the mock relay will not know about it.
|
|
51
|
-
* Therefore, subsequent calls to emitOK() may send a duplicate OK message.
|
|
52
|
-
* */
|
|
53
|
-
emit(message: unknown, socket?: MockServerSocket): string;
|
|
54
|
-
/**
|
|
55
|
-
* Send COUNT messages from all socket mocks
|
|
56
|
-
* holding active COUNT subscriptions (in other words,
|
|
57
|
-
* subscriptions that have not yet responded with COUNT)
|
|
58
|
-
* with the given subId.
|
|
59
|
-
*/
|
|
60
|
-
emitCOUNT(subId: string, count?: number): Nostr.ToClientMessage.COUNT;
|
|
61
|
-
/**
|
|
62
|
-
* Send EOSE messages from all socket mocks
|
|
63
|
-
* holding active REQ subscriptions (in other words,
|
|
64
|
-
* subscriptions that have not yet been CLOSE'd)
|
|
65
|
-
* with the given subId.
|
|
66
|
-
*/
|
|
67
|
-
emitEOSE(subId: string): Nostr.ToClientMessage.EOSE;
|
|
68
|
-
/**
|
|
69
|
-
* Send EVENT messages from all socket mocks
|
|
70
|
-
* holding active REQ subscriptions (in other words,
|
|
71
|
-
* subscriptions that have not yet been CLOSE'd)
|
|
72
|
-
* with the given subId.
|
|
73
|
-
*/
|
|
74
|
-
emitEVENT(subId: string, event?: Partial<Nostr.Event>): Nostr.ToClientMessage.EVENT;
|
|
75
|
-
/**
|
|
76
|
-
* Send OK messages from all socket mocks
|
|
77
|
-
* holding active EVENT (in other words, an EVENT
|
|
78
|
-
* that has not yet returned an OK message)
|
|
79
|
-
* with the given eventId.
|
|
80
|
-
*/
|
|
81
|
-
emitOK(eventId: string, succeeded: boolean, message?: string): Nostr.ToClientMessage.OK;
|
|
82
|
-
}
|
|
83
|
-
export declare function createMockRelay(url: string): MockRelay;
|
|
84
|
-
export {};
|
package/dist/__tests__/mock.js
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { WS } from "vitest-websocket-mock";
|
|
2
|
-
import { faker } from "./faker.js";
|
|
3
|
-
import { withTimeout } from "./utils.js";
|
|
4
|
-
export function createMockServer(url, behavior) {
|
|
5
|
-
const server = new WS(url, { jsonProtocol: true });
|
|
6
|
-
const sockets = [];
|
|
7
|
-
let resolvers = [];
|
|
8
|
-
let nextId = 1;
|
|
9
|
-
server.on("connection", (_socket) => {
|
|
10
|
-
const socket = {
|
|
11
|
-
id: nextId++,
|
|
12
|
-
send(message) {
|
|
13
|
-
if (_socket.readyState !== WebSocket.OPEN) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
_socket.send(typeof message === "string" ? message : `${JSON.stringify(message)}`);
|
|
17
|
-
},
|
|
18
|
-
close(options) {
|
|
19
|
-
_socket.close(options);
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
sockets.push(socket);
|
|
23
|
-
resolvers.filter(([count]) => count <= sockets.length).forEach(([, resolve]) => resolve([...sockets]));
|
|
24
|
-
resolvers = resolvers.filter(([count]) => count > sockets.length);
|
|
25
|
-
behavior.onOpen?.(socket);
|
|
26
|
-
_socket.on("message", (message) => {
|
|
27
|
-
if (typeof message !== "string") {
|
|
28
|
-
throw new Error("Unexpected type message");
|
|
29
|
-
}
|
|
30
|
-
behavior.onMessage?.(socket, message);
|
|
31
|
-
});
|
|
32
|
-
_socket.on("close", () => {
|
|
33
|
-
behavior.onClose?.(socket);
|
|
34
|
-
});
|
|
35
|
-
_socket.on("error", (error) => {
|
|
36
|
-
behavior.onError?.(socket, error);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
const getSockets = async (count, options) => {
|
|
40
|
-
const promise = new Promise((resolve) => {
|
|
41
|
-
if (sockets.length >= count) {
|
|
42
|
-
resolve([...sockets]);
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
resolvers.push([count, resolve]);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
return withTimeout(promise, `Mock relay was waiting for ${count} connections to be established, but timed out.`, options?.timeout);
|
|
49
|
-
};
|
|
50
|
-
const getSocket = async (index) => {
|
|
51
|
-
const sockets = await getSockets(index + 1);
|
|
52
|
-
return sockets[index];
|
|
53
|
-
};
|
|
54
|
-
return Object.assign(server, {
|
|
55
|
-
getSockets,
|
|
56
|
-
getSocket,
|
|
57
|
-
getSocketsSync: () => [...sockets],
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
function relayBehavior() {
|
|
61
|
-
const reqs = new Map();
|
|
62
|
-
const counts = new Map();
|
|
63
|
-
const events = new Map();
|
|
64
|
-
const sockets = new Set();
|
|
65
|
-
return {
|
|
66
|
-
onOpen(socket) {
|
|
67
|
-
sockets.add(socket);
|
|
68
|
-
},
|
|
69
|
-
onMessage(socket, rawMessage) {
|
|
70
|
-
const message = JSON.parse(rawMessage);
|
|
71
|
-
switch (message[0]) {
|
|
72
|
-
case "CLOSE": {
|
|
73
|
-
const subId = message[1];
|
|
74
|
-
reqs.get(socket)?.delete(subId);
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
case "COUNT": {
|
|
78
|
-
if (!counts.has(socket)) {
|
|
79
|
-
counts.set(socket, new Set());
|
|
80
|
-
}
|
|
81
|
-
const subId = message[1];
|
|
82
|
-
counts.get(socket)?.add(subId);
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
case "EVENT": {
|
|
86
|
-
if (!events.has(socket)) {
|
|
87
|
-
events.set(socket, new Set());
|
|
88
|
-
}
|
|
89
|
-
const eventId = message[1].id;
|
|
90
|
-
events.get(socket)?.add(eventId);
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
case "REQ": {
|
|
94
|
-
if (!reqs.has(socket)) {
|
|
95
|
-
reqs.set(socket, new Set());
|
|
96
|
-
}
|
|
97
|
-
const subId = message[1];
|
|
98
|
-
reqs.get(socket)?.add(subId);
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
onClose(socket) {
|
|
104
|
-
reqs.delete(socket);
|
|
105
|
-
counts.delete(socket);
|
|
106
|
-
events.delete(socket);
|
|
107
|
-
sockets.delete(socket);
|
|
108
|
-
},
|
|
109
|
-
emit(message, socket) {
|
|
110
|
-
const toBeSent = typeof message === "string" ? message : `${JSON.stringify(message)}`;
|
|
111
|
-
if (socket) {
|
|
112
|
-
socket.send(toBeSent);
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
for (const socket of sockets) {
|
|
116
|
-
socket.send(toBeSent);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return toBeSent;
|
|
120
|
-
},
|
|
121
|
-
emitCOUNT(subId, count) {
|
|
122
|
-
const toBeSent = faker.toClientMessage.COUNT(subId, count);
|
|
123
|
-
for (const [socket, subIds] of counts.entries()) {
|
|
124
|
-
if (subIds.has(subId)) {
|
|
125
|
-
socket.send(toBeSent);
|
|
126
|
-
}
|
|
127
|
-
subIds.delete(subId);
|
|
128
|
-
}
|
|
129
|
-
return toBeSent;
|
|
130
|
-
},
|
|
131
|
-
emitEOSE(subId) {
|
|
132
|
-
const toBeSent = faker.toClientMessage.EOSE(subId);
|
|
133
|
-
for (const [socket, subIds] of reqs.entries()) {
|
|
134
|
-
if (subIds.has(subId)) {
|
|
135
|
-
socket.send(toBeSent);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return toBeSent;
|
|
139
|
-
},
|
|
140
|
-
emitEVENT(subId, event) {
|
|
141
|
-
const toBeSent = faker.toClientMessage.EVENT(subId, event);
|
|
142
|
-
for (const [socket, subIds] of reqs.entries()) {
|
|
143
|
-
if (subIds.has(subId)) {
|
|
144
|
-
socket.send(toBeSent);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return toBeSent;
|
|
148
|
-
},
|
|
149
|
-
emitOK(eventId, succeeded, message) {
|
|
150
|
-
const toBeSent = faker.toClientMessage.OK(eventId, succeeded, message);
|
|
151
|
-
for (const [socket, eventIds] of events.entries()) {
|
|
152
|
-
if (eventIds.has(eventId)) {
|
|
153
|
-
socket.send(toBeSent);
|
|
154
|
-
}
|
|
155
|
-
eventIds.delete(eventId);
|
|
156
|
-
}
|
|
157
|
-
return toBeSent;
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
export function createMockRelay(url) {
|
|
162
|
-
const behavior = relayBehavior();
|
|
163
|
-
const server = createMockServer(url, behavior);
|
|
164
|
-
const { emit, emitCOUNT, emitEOSE, emitEVENT, emitOK } = behavior;
|
|
165
|
-
const timeoutMessage = "Mock relay was waiting for the next message from the client, but timed out.";
|
|
166
|
-
return Object.assign(server, {
|
|
167
|
-
next: (timeout) => withTimeout(server.nextMessage, timeoutMessage, timeout),
|
|
168
|
-
nexts: (count, timeout) => withTimeout(async () => {
|
|
169
|
-
const messages = [];
|
|
170
|
-
for (let i = 0; i < count; i++) {
|
|
171
|
-
messages.push((await server.nextMessage));
|
|
172
|
-
}
|
|
173
|
-
return messages;
|
|
174
|
-
}, timeoutMessage, timeout),
|
|
175
|
-
emit,
|
|
176
|
-
emitCOUNT,
|
|
177
|
-
emitEOSE,
|
|
178
|
-
emitEVENT,
|
|
179
|
-
emitOK,
|
|
180
|
-
});
|
|
181
|
-
}
|
package/dist/__tests__/spy.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import Nostr from "nostr-typedef";
|
|
2
|
-
export interface ClientSpy {
|
|
3
|
-
__createClientSpy: true;
|
|
4
|
-
next: () => Promise<Nostr.ToClientMessage.Any>;
|
|
5
|
-
dispose: () => void;
|
|
6
|
-
}
|
|
7
|
-
export declare function createClientSpy(addOnMessageListener: (listener: (message: Nostr.ToClientMessage.Any) => void) => void): ClientSpy;
|
package/dist/__tests__/spy.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
export function createClientSpy(addOnMessageListener) {
|
|
2
|
-
let queue = { type: "empty" };
|
|
3
|
-
addOnMessageListener((message) => {
|
|
4
|
-
switch (queue.type) {
|
|
5
|
-
case "empty": {
|
|
6
|
-
queue = {
|
|
7
|
-
type: "message",
|
|
8
|
-
items: [message],
|
|
9
|
-
};
|
|
10
|
-
break;
|
|
11
|
-
}
|
|
12
|
-
case "message": {
|
|
13
|
-
queue.items.push(message);
|
|
14
|
-
break;
|
|
15
|
-
}
|
|
16
|
-
case "resolver": {
|
|
17
|
-
const item = queue.items.shift();
|
|
18
|
-
if (item) {
|
|
19
|
-
const resolve = item[0];
|
|
20
|
-
resolve(message);
|
|
21
|
-
}
|
|
22
|
-
if (queue.items.length <= 0) {
|
|
23
|
-
queue = { type: "empty" };
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
return {
|
|
29
|
-
__createClientSpy: true,
|
|
30
|
-
next: () => new Promise((resolve, reject) => {
|
|
31
|
-
switch (queue.type) {
|
|
32
|
-
case "empty": {
|
|
33
|
-
queue = {
|
|
34
|
-
type: "resolver",
|
|
35
|
-
items: [[resolve, reject]],
|
|
36
|
-
};
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
case "message": {
|
|
40
|
-
const msg = queue.items.shift();
|
|
41
|
-
if (msg) {
|
|
42
|
-
resolve(msg);
|
|
43
|
-
}
|
|
44
|
-
if (queue.items.length <= 0) {
|
|
45
|
-
queue = { type: "empty" };
|
|
46
|
-
}
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
case "resolver": {
|
|
50
|
-
queue.items.push([resolve, reject]);
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}),
|
|
55
|
-
dispose: () => {
|
|
56
|
-
if (queue.type === "resolver") {
|
|
57
|
-
for (const item of queue.items) {
|
|
58
|
-
const reject = item[1];
|
|
59
|
-
reject(new Error());
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
queue = { type: "empty" };
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}
|
package/dist/__tests__/utils.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export class TimeoutError extends Error {
|
|
2
|
-
}
|
|
3
|
-
export function withTimeout(task, message, timeout = 1000) {
|
|
4
|
-
return Promise.race([
|
|
5
|
-
typeof task === "function" ? task() : task,
|
|
6
|
-
new Promise((_, reject) => setTimeout(() => reject(new TimeoutError(message)), timeout)),
|
|
7
|
-
]);
|
|
8
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
const IncomingREQ = z.tuple([z.literal("REQ"), z.string(), z.array(z.any())]);
|
|
3
|
-
const IncomingEVENT = z.tuple([z.literal("EVENT"), z.any()]);
|
|
4
|
-
const IncomingCLOSE = z.tuple([z.literal("CLOSE"), z.string()]);
|
|
5
|
-
const IncomingAUTH = z.tuple([z.literal("AUTH"), z.any()]);
|
|
6
|
-
const OutgoingEVENT = z.tuple([z.literal("EVENT"), z.string(), z.any()]);
|
|
7
|
-
const OutgoingNOTICE = z.tuple([z.literal("NOTICE"), z.string()]);
|
|
8
|
-
const OutgoingCLOSE = z.tuple([z.literal("CLOSE"), z.string()]);
|
|
9
|
-
const OutgoingAUTH = z.tuple([z.literal("AUTH"), z.string()]);
|