applesauce-relay 1.1.0 → 2.0.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.
@@ -1,611 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
- import { subscribeSpyTo } from "@hirez_io/observer-spy";
3
- import { getSeenRelays } from "applesauce-core/helpers";
4
- import { WS } from "vitest-websocket-mock";
5
- import { Relay } from "../relay.js";
6
- import { filter } from "rxjs/operators";
7
- import { firstValueFrom, of, throwError, timer } from "rxjs";
8
- const defaultMockInfo = {
9
- name: "Test Relay",
10
- description: "Test Relay Description",
11
- pubkey: "testpubkey",
12
- contact: "test@example.com",
13
- supported_nips: [1, 2, 3],
14
- software: "test-software",
15
- version: "1.0.0",
16
- };
17
- let server;
18
- let relay;
19
- beforeEach(async () => {
20
- // Mock empty information document
21
- vi.spyOn(Relay, "fetchInformationDocument").mockImplementation(() => of(null));
22
- // Create mock relay
23
- server = new WS("wss://test", { jsonProtocol: true });
24
- // Create relay
25
- relay = new Relay("wss://test");
26
- relay.keepAlive = 0;
27
- });
28
- // Wait for server to close to prevent memory leaks
29
- afterEach(async () => {
30
- await WS.clean();
31
- vi.clearAllTimers();
32
- vi.useRealTimers();
33
- });
34
- const mockEvent = {
35
- kind: 1,
36
- id: "00007641c9c3e65a71843933a44a18060c7c267a4f9169efa3735ece45c8f621",
37
- pubkey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
38
- created_at: 1743712795,
39
- tags: [["nonce", "13835058055282167643", "16"]],
40
- content: "This is just stupid: https://codestr.fiatjaf.com/",
41
- sig: "5a57b5a12bba4b7cf0121077b1421cf4df402c5c221376c076204fc4f7519e28ce6508f26ddc132c406ccfe6e62cc6db857b96c788565cdca9674fe9a0710ac2",
42
- };
43
- describe("req", () => {
44
- it("should trigger connection to relay", async () => {
45
- subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
46
- // Wait for connection
47
- await firstValueFrom(relay.connected$.pipe(filter(Boolean)));
48
- expect(relay.connected).toBe(true);
49
- });
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 () => {
57
- // Create subscription that completes after first EOSE
58
- const sub = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
59
- // Verify REQ was sent
60
- await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
61
- // Send EOSE to complete subscription
62
- server.send(["EVENT", "sub1", mockEvent]);
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] }]);
73
- // Complete the subscription
74
- sub.unsubscribe();
75
- // Verify CLOSE was sent
76
- await expect(server).toReceiveMessage(["CLOSE", "sub1"]);
77
- });
78
- it("should emit nostr event and EOSE", async () => {
79
- const spy = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
80
- await server.connected;
81
- // Send EVENT message
82
- server.send(["EVENT", "sub1", mockEvent]);
83
- // Send EOSE message
84
- server.send(["EOSE", "sub1"]);
85
- expect(spy.getValues()).toEqual([expect.objectContaining(mockEvent), "EOSE"]);
86
- });
87
- it("should ignore EVENT and EOSE messages that do not match subscription id", async () => {
88
- const spy = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
89
- await server.connected;
90
- // Send EVENT message with wrong subscription id
91
- server.send(["EVENT", "wrong_sub", mockEvent]);
92
- // Send EOSE message with wrong subscription id
93
- server.send(["EOSE", "wrong_sub"]);
94
- // Send EVENT message with correct subscription id
95
- server.send(["EVENT", "sub1", mockEvent]);
96
- // Send EOSE message with correct subscription id
97
- server.send(["EOSE", "sub1"]);
98
- expect(spy.getValues()).toEqual([expect.objectContaining(mockEvent), "EOSE"]);
99
- });
100
- it("should mark events with their source relay", async () => {
101
- const spy = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
102
- await server.connected;
103
- // Send EVENT message
104
- server.send(["EVENT", "sub1", mockEvent]);
105
- // Get the received event
106
- const receivedEvent = spy.getValues()[0];
107
- // Verify the event was marked as seen from this relay
108
- expect(getSeenRelays(receivedEvent)).toContain("wss://test");
109
- });
110
- it("should error subscription when CLOSED message is received", async () => {
111
- const spy = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"), { expectErrors: true });
112
- await server.connected;
113
- // Send CLOSED message for the subscription
114
- server.send(["CLOSED", "sub1", "reason"]);
115
- // Verify the subscription completed
116
- expect(spy.receivedError()).toBe(true);
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
- });
126
- it("should wait for authentication if relay responds with auth-required", async () => {
127
- // First subscription to trigger auth-required
128
- const firstSub = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"), { expectErrors: true });
129
- await server.nextMessage;
130
- // Send CLOSED message with auth-required reason
131
- server.send(["CLOSED", "sub1", "auth-required: need to authenticate"]);
132
- // wait for complete
133
- await firstSub.onError();
134
- await server.nextMessage;
135
- // Create a second subscription that should wait for auth
136
- const secondSub = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub2"), { expectErrors: true });
137
- // Verify no REQ message was sent yet (waiting for auth)
138
- expect(server).not.toHaveReceivedMessages(["REQ", "sub2", { kinds: [1] }]);
139
- // Simulate successful authentication
140
- relay.authenticated$.next(true);
141
- // Now the REQ should be sent
142
- await expect(server).toReceiveMessage(["REQ", "sub2", { kinds: [1] }]);
143
- // Send EVENT and EOSE to complete the subscription
144
- server.send(["EVENT", "sub2", mockEvent]);
145
- server.send(["EOSE", "sub2"]);
146
- // Verify the second subscription received the event and EOSE
147
- expect(secondSub.getValues()).toEqual([expect.objectContaining(mockEvent), "EOSE"]);
148
- });
149
- it("should wait for authentication if relay info document has limitations.auth_required = true", async () => {
150
- // Mock the fetchInformationDocument method to return a document with auth_required = true
151
- vi.spyOn(Relay, "fetchInformationDocument").mockImplementation(() => of({
152
- name: "Auth Required Relay",
153
- description: "A relay that requires authentication",
154
- pubkey: "",
155
- contact: "",
156
- supported_nips: [1, 2, 4],
157
- software: "",
158
- version: "",
159
- limitation: {
160
- auth_required: true,
161
- },
162
- }));
163
- // Create a subscription that should wait for auth
164
- const sub = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
165
- // Wait 10ms to ensure the information document is fetched
166
- await new Promise((resolve) => setTimeout(resolve, 10));
167
- // Verify no REQ message was sent yet (waiting for auth)
168
- expect(server).not.toHaveReceivedMessages(["REQ", "sub1", { kinds: [1] }]);
169
- // Simulate successful authentication
170
- relay.authenticated$.next(true);
171
- // Now the REQ should be sent
172
- await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
173
- // Send EVENT and EOSE to complete the subscription
174
- server.send(["EVENT", "sub1", mockEvent]);
175
- server.send(["EOSE", "sub1"]);
176
- // Verify the subscription received the event and EOSE
177
- expect(sub.getValues()).toEqual([expect.objectContaining(mockEvent), "EOSE"]);
178
- });
179
- it("should throw error if relay closes connection with error", async () => {
180
- const spy = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"), { expectErrors: true });
181
- await server.connected;
182
- // Send CLOSE message with error
183
- server.error({
184
- reason: "error message",
185
- code: 1000,
186
- wasClean: false,
187
- });
188
- // Verify the subscription completed with an error
189
- expect(spy.receivedError()).toBe(true);
190
- });
191
- it("should not return EOSE while waiting for the relay to be ready", async () => {
192
- vi.useFakeTimers();
193
- // @ts-expect-error
194
- relay.ready$.next(false);
195
- const spy = subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"), { expectErrors: true });
196
- // Fast-forward time by 20 seconds
197
- await vi.advanceTimersByTimeAsync(20000);
198
- expect(spy.receivedComplete()).toBe(false);
199
- expect(spy.receivedError()).toBe(false);
200
- expect(spy.receivedNext()).toBe(false);
201
- });
202
- it("should wait when relay isn't ready", async () => {
203
- // @ts-expect-error
204
- relay.ready$.next(false);
205
- subscribeSpyTo(relay.req([{ kinds: [1] }], "sub1"));
206
- // Wait 10ms to ensure the relay didn't receive anything
207
- await new Promise((resolve) => setTimeout(resolve, 10));
208
- expect(server.messages.length).toBe(0);
209
- // @ts-expect-error
210
- relay.ready$.next(true);
211
- await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
212
- });
213
- });
214
- describe("event", () => {
215
- it("should wait for authentication if relay responds with auth-required", async () => {
216
- // First event to trigger auth-required
217
- const firstSpy = subscribeSpyTo(relay.event(mockEvent));
218
- await expect(server).toReceiveMessage(["EVENT", mockEvent]);
219
- // Send OK with auth-required message
220
- server.send(["OK", mockEvent.id, false, "auth-required: need to authenticate"]);
221
- await firstSpy.onComplete();
222
- // Create a second event that should wait for auth
223
- const secondSpy = subscribeSpyTo(relay.event(mockEvent));
224
- // Verify no EVENT message was sent yet (waiting for auth)
225
- expect(server).not.toHaveReceivedMessages(["EVENT", mockEvent]);
226
- // Simulate successful authentication
227
- relay.authenticated$.next(true);
228
- // Now the EVENT should be sent
229
- await expect(server).toReceiveMessage(["EVENT", mockEvent]);
230
- // Send OK response to complete the event
231
- server.send(["OK", mockEvent.id, true, ""]);
232
- // Verify the second event completed successfully
233
- await secondSpy.onComplete();
234
- expect(secondSpy.receivedComplete()).toBe(true);
235
- });
236
- it("should wait for authentication if relay info document has limitations.auth_required = true", async () => {
237
- // Mock the fetchInformationDocument method to return a document with auth_required = true
238
- vi.spyOn(Relay, "fetchInformationDocument").mockImplementation(() => of({
239
- name: "Auth Required Relay",
240
- description: "A relay that requires authentication",
241
- pubkey: "",
242
- contact: "",
243
- supported_nips: [1, 2, 4],
244
- software: "",
245
- version: "",
246
- limitation: {
247
- auth_required: true,
248
- },
249
- }));
250
- // Create a subscription that should wait for auth
251
- const sub = subscribeSpyTo(relay.event(mockEvent));
252
- // Wait 10ms to ensure the information document is fetched
253
- await new Promise((resolve) => setTimeout(resolve, 10));
254
- // Verify no REQ message was sent yet (waiting for auth)
255
- expect(server).not.toHaveReceivedMessages(["EVENT", mockEvent]);
256
- // Simulate successful authentication
257
- relay.authenticated$.next(true);
258
- // Now the REQ should be sent
259
- await expect(server).toReceiveMessage(["EVENT", mockEvent]);
260
- // Send EVENT and EOSE to complete the subscription
261
- server.send(["OK", mockEvent.id, true, ""]);
262
- // Verify the subscription completed
263
- await sub.onComplete();
264
- expect(sub.receivedComplete()).toBe(true);
265
- });
266
- it("should trigger connection to relay", async () => {
267
- subscribeSpyTo(relay.event(mockEvent));
268
- // Wait for connection
269
- await firstValueFrom(relay.connected$.pipe(filter(Boolean)));
270
- expect(relay.connected).toBe(true);
271
- });
272
- it("observable should complete when matching OK response received", async () => {
273
- const spy = subscribeSpyTo(relay.event(mockEvent));
274
- // Verify EVENT message was sent
275
- expect(await server.nextMessage).toEqual(["EVENT", mockEvent]);
276
- // Send matching OK response
277
- server.send(["OK", mockEvent.id, true, ""]);
278
- await spy.onComplete();
279
- expect(spy.receivedComplete()).toBe(true);
280
- });
281
- it("should ignore OK responses for different events", async () => {
282
- const spy = subscribeSpyTo(relay.event(mockEvent));
283
- await server.connected;
284
- // Send non-matching OK response
285
- server.send(["OK", "different_id", true, ""]);
286
- expect(spy.receivedComplete()).toBe(false);
287
- // Send matching OK response
288
- server.send(["OK", mockEvent.id, true, ""]);
289
- expect(spy.receivedComplete()).toBe(true);
290
- });
291
- it("should send EVENT message to relay", async () => {
292
- relay.event(mockEvent).subscribe();
293
- expect(await server.nextMessage).toEqual(["EVENT", mockEvent]);
294
- });
295
- it("should complete with error if no OK received within 10s", async () => {
296
- vi.useFakeTimers();
297
- const spy = subscribeSpyTo(relay.event(mockEvent));
298
- // Fast-forward time by 10 seconds
299
- await vi.advanceTimersByTimeAsync(10000);
300
- expect(spy.receivedComplete()).toBe(true);
301
- expect(spy.getLastValue()).toEqual({ ok: false, from: "wss://test", message: "Timeout" });
302
- });
303
- it("should throw error if relay closes connection with error", async () => {
304
- const spy = subscribeSpyTo(relay.event(mockEvent), { expectErrors: true });
305
- await server.connected;
306
- // Send CLOSE message with error
307
- server.error({
308
- reason: "error message",
309
- code: 1000,
310
- wasClean: false,
311
- });
312
- // Verify the subscription completed with an error
313
- expect(spy.receivedError()).toBe(true);
314
- });
315
- it("should not throw a timeout error while waiting for the relay to be ready", async () => {
316
- vi.useFakeTimers();
317
- // @ts-expect-error
318
- relay.ready$.next(false);
319
- const spy = subscribeSpyTo(relay.event(mockEvent), { expectErrors: true });
320
- // Fast-forward time by 20 seconds
321
- await vi.advanceTimersByTimeAsync(20000);
322
- expect(spy.receivedComplete()).toBe(false);
323
- expect(spy.receivedError()).toBe(false);
324
- });
325
- it("should wait when relay isn't ready", async () => {
326
- // @ts-expect-error
327
- relay.ready$.next(false);
328
- subscribeSpyTo(relay.event(mockEvent));
329
- // Wait 10ms to ensure the relay didn't receive anything
330
- await new Promise((resolve) => setTimeout(resolve, 10));
331
- expect(server.messages.length).toBe(0);
332
- // @ts-expect-error
333
- relay.ready$.next(true);
334
- await expect(server).toReceiveMessage(["EVENT", mockEvent]);
335
- });
336
- });
337
- describe("notices$", () => {
338
- it("should not trigger connection to relay", async () => {
339
- subscribeSpyTo(relay.notices$);
340
- expect(relay.connected).toBe(false);
341
- });
342
- it("should accumulate notices in notices$ state", async () => {
343
- subscribeSpyTo(relay.req({ kinds: [1] }));
344
- // Send multiple NOTICE messages
345
- server.send(["NOTICE", "Notice 1"]);
346
- server.send(["NOTICE", "Notice 2"]);
347
- server.send(["NOTICE", "Notice 3"]);
348
- // Verify the notices state contains all messages
349
- expect(relay.notices$.value).toEqual(["Notice 1", "Notice 2", "Notice 3"]);
350
- });
351
- it("should ignore non-NOTICE messages", async () => {
352
- subscribeSpyTo(relay.req({ kinds: [1] }));
353
- server.send(["NOTICE", "Important notice"]);
354
- server.send(["OTHER", "other message"]);
355
- // Verify only NOTICE messages are in the state
356
- expect(relay.notices$.value).toEqual(["Important notice"]);
357
- });
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
- });
408
- describe("challenge$", () => {
409
- it("should not trigger connection to relay", async () => {
410
- subscribeSpyTo(relay.challenge$);
411
- await new Promise((resolve) => setTimeout(resolve, 10));
412
- expect(relay.connected).toBe(false);
413
- });
414
- it("should set challenge$ when AUTH message received", async () => {
415
- subscribeSpyTo(relay.req({ kinds: [1] }));
416
- // Send AUTH message with challenge string
417
- server.send(["AUTH", "challenge-string-123"]);
418
- // Verify challenge$ was set
419
- expect(relay.challenge$.value).toBe("challenge-string-123");
420
- });
421
- it("should ignore non-AUTH messages", async () => {
422
- subscribeSpyTo(relay.req({ kinds: [1] }));
423
- server.send(["NOTICE", "Not a challenge"]);
424
- server.send(["OTHER", "other message"]);
425
- // Verify challenge$ remains null
426
- expect(relay.challenge$.value).toBe(null);
427
- });
428
- });
429
- describe("information$", () => {
430
- it("should fetch information document when information$ is subscribed to", async () => {
431
- // Mock the fetchInformationDocument method
432
- const mockInfo = { ...defaultMockInfo, limitation: { auth_required: false } };
433
- vi.spyOn(Relay, "fetchInformationDocument").mockReturnValue(of(mockInfo));
434
- // Subscribe to information$
435
- const sub = subscribeSpyTo(relay.information$);
436
- // Verify fetchInformationDocument was called with the relay URL
437
- expect(Relay.fetchInformationDocument).toHaveBeenCalledWith(relay.url);
438
- // Verify the information was emitted
439
- expect(sub.getLastValue()).toEqual(mockInfo);
440
- });
441
- it("should return null when fetchInformationDocument fails", async () => {
442
- // Mock the fetchInformationDocument method to throw an error
443
- vi.spyOn(Relay, "fetchInformationDocument").mockReturnValue(throwError(() => new Error("Failed to fetch")));
444
- // Subscribe to information$
445
- const sub = subscribeSpyTo(relay.information$);
446
- // Verify fetchInformationDocument was called
447
- expect(Relay.fetchInformationDocument).toHaveBeenCalled();
448
- // Verify null was emitted
449
- expect(sub.getLastValue()).toBeNull();
450
- });
451
- it("should cache the information document", async () => {
452
- // Mock the fetchInformationDocument method
453
- const mockInfo = { ...defaultMockInfo, limitation: { auth_required: true } };
454
- vi.spyOn(Relay, "fetchInformationDocument").mockReturnValue(of(mockInfo));
455
- // Subscribe to information$ multiple times
456
- const sub1 = subscribeSpyTo(relay.information$);
457
- const sub2 = subscribeSpyTo(relay.information$);
458
- // Verify fetchInformationDocument was called only once
459
- expect(Relay.fetchInformationDocument).toHaveBeenCalledTimes(1);
460
- // Verify both subscriptions received the same information
461
- expect(sub1.getLastValue()).toEqual(mockInfo);
462
- expect(sub2.getLastValue()).toEqual(mockInfo);
463
- // Verify the internal state was updated
464
- expect(relay.information).toEqual(mockInfo);
465
- });
466
- });
467
- describe("createReconnectTimer", () => {
468
- it("should create a reconnect timer when relay closes with error", async () => {
469
- const reconnectTimer = vi.fn().mockReturnValue(timer(1000));
470
- vi.spyOn(Relay, "createReconnectTimer").mockReturnValue(reconnectTimer);
471
- relay = new Relay("wss://test");
472
- const spy = subscribeSpyTo(relay.req([{ kinds: [1] }]), { expectErrors: true });
473
- // Send CLOSE message with error
474
- server.error({
475
- reason: "error message",
476
- code: 1000,
477
- wasClean: false,
478
- });
479
- // Verify the subscription errored
480
- expect(spy.receivedError()).toBe(true);
481
- expect(reconnectTimer).toHaveBeenCalledWith(expect.any(Error), 0);
482
- });
483
- it("should set ready$ to false until the reconnect timer completes", async () => {
484
- vi.useFakeTimers();
485
- const reconnectTimer = vi.fn().mockReturnValue(timer(1000));
486
- vi.spyOn(Relay, "createReconnectTimer").mockReturnValue(reconnectTimer);
487
- relay = new Relay("wss://test");
488
- subscribeSpyTo(relay.req([{ kinds: [1] }]), { expectErrors: true });
489
- // Send CLOSE message with error
490
- server.error({
491
- reason: "error message",
492
- code: 1000,
493
- wasClean: false,
494
- });
495
- // @ts-expect-error
496
- expect(relay.ready$.value).toBe(false);
497
- // Fast-forward time by 10ms
498
- await vi.advanceTimersByTimeAsync(5000);
499
- // @ts-expect-error
500
- expect(relay.ready$.value).toBe(true);
501
- });
502
- });
503
- describe("publish", () => {
504
- it("should retry when auth-required is received and authentication is completed", async () => {
505
- // First attempt to publish
506
- const spy = subscribeSpyTo(relay.publish(mockEvent));
507
- // Verify EVENT was sent
508
- await expect(server).toReceiveMessage(["EVENT", mockEvent]);
509
- // Send auth-required response
510
- server.send(["AUTH", "challenge-string"]);
511
- server.send(["OK", mockEvent.id, false, "auth-required: need to authenticate"]);
512
- // Send auth event
513
- const authEvent = { ...mockEvent, id: "auth-id" };
514
- subscribeSpyTo(relay.auth(authEvent));
515
- // Verify AUTH was sent
516
- await expect(server).toReceiveMessage(["AUTH", authEvent]);
517
- // Send successful auth response
518
- server.send(["OK", authEvent.id, true, ""]);
519
- // Wait for the event to be sent again
520
- await expect(server).toReceiveMessage(["EVENT", mockEvent]);
521
- // Send successful response for the retried event
522
- server.send(["OK", mockEvent.id, true, ""]);
523
- // Verify the final result is successful
524
- expect(spy.getLastValue()).toEqual({ ok: true, message: "", from: "wss://test" });
525
- });
526
- it("should error after max retries", async () => {
527
- const spy = subscribeSpyTo(relay.publish(mockEvent, { retries: 0 }), { expectErrors: true });
528
- // Close with error
529
- server.error({ reason: "error message", code: 1000, wasClean: false });
530
- // Verify the subscription errored
531
- expect(spy.receivedError()).toBe(true);
532
- });
533
- });
534
- describe("request", () => {
535
- it("should retry when auth-required is received and authentication is completed", async () => {
536
- // First attempt to request
537
- const spy = subscribeSpyTo(relay.request({ kinds: [1] }, { id: "sub1" }));
538
- // Verify REQ was sent
539
- await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
540
- // Send auth-required response
541
- server.send(["AUTH", "challenge-string"]);
542
- server.send(["CLOSED", "sub1", "auth-required: need to authenticate"]);
543
- // Wait for subscription to close
544
- await expect(server).toReceiveMessage(["CLOSE", "sub1"]);
545
- // Send auth event
546
- const authEvent = { ...mockEvent, id: "auth-id" };
547
- const authSpy = subscribeSpyTo(relay.auth(authEvent));
548
- // Verify AUTH was sent
549
- await expect(server).toReceiveMessage(["AUTH", authEvent]);
550
- server.send(["OK", authEvent.id, true, ""]);
551
- // Wait for auth to complete
552
- await authSpy.onComplete();
553
- // Wait for retry
554
- await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
555
- // Send response
556
- server.send(["EVENT", "sub1", mockEvent]);
557
- server.send(["EOSE", "sub1"]);
558
- // Verify the final result is successful
559
- expect(spy.getLastValue()).toEqual(expect.objectContaining(mockEvent));
560
- expect(spy.receivedComplete()).toBe(true);
561
- });
562
- });
563
- describe("subscription", () => {
564
- it("should retry when auth-required is received and authentication is completed", async () => {
565
- // First attempt to request
566
- const spy = subscribeSpyTo(relay.subscription({ kinds: [1] }, { id: "sub1" }));
567
- // Verify REQ was sent
568
- await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
569
- // Send auth-required response
570
- server.send(["AUTH", "challenge-string"]);
571
- server.send(["CLOSED", "sub1", "auth-required: need to authenticate"]);
572
- // Wait for subscription to close
573
- await expect(server).toReceiveMessage(["CLOSE", "sub1"]);
574
- // Send auth event
575
- const authEvent = { ...mockEvent, id: "auth-id" };
576
- const authSpy = subscribeSpyTo(relay.auth(authEvent));
577
- // Verify AUTH was sent
578
- await expect(server).toReceiveMessage(["AUTH", authEvent]);
579
- server.send(["OK", authEvent.id, true, ""]);
580
- // Wait for auth to complete
581
- await authSpy.onComplete();
582
- // Wait for retry
583
- await expect(server).toReceiveMessage(["REQ", "sub1", { kinds: [1] }]);
584
- // Send response
585
- server.send(["EVENT", "sub1", mockEvent]);
586
- server.send(["EOSE", "sub1"]);
587
- // Verify the final result is successful
588
- expect(spy.getValues()).toEqual([expect.objectContaining(mockEvent), "EOSE"]);
589
- expect(spy.receivedComplete()).toBe(false);
590
- });
591
- });
592
- // describe("keepAlive", () => {
593
- // it("should close the socket connection after keepAlive timeout", async () => {
594
- // vi.useFakeTimers();
595
- // // Set a short keepAlive timeout for testing
596
- // relay.keepAlive = 100; // 100ms for quick testing
597
- // // Subscribe to the relay to ensure it is active
598
- // const sub = subscribeSpyTo(relay.req([{ kinds: [1] }]));
599
- // // Wait for connection
600
- // await server.connected;
601
- // // Close the subscription
602
- // sub.unsubscribe();
603
- // // Fast-forward time by 10ms
604
- // await vi.advanceTimersByTimeAsync(10);
605
- // // should still be connected
606
- // expect(relay.connected).toBe(true);
607
- // // Wait for the keepAlive timeout to elapse
608
- // await vi.advanceTimersByTimeAsync(150);
609
- // expect(relay.connected).toBe(false);
610
- // });
611
- // });