applesauce-relay 0.0.0-next-20250808173123 → 0.0.0-next-20250828144630
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__/auth.test.d.ts +1 -0
- package/dist/__tests__/auth.test.js +111 -0
- package/dist/__tests__/exports.test.d.ts +1 -0
- package/dist/__tests__/exports.test.js +19 -0
- package/dist/__tests__/fake-user.d.ts +10 -0
- package/dist/__tests__/fake-user.js +31 -0
- package/dist/__tests__/faker.d.ts +19 -0
- package/dist/__tests__/faker.js +56 -0
- package/dist/__tests__/group.test.d.ts +1 -0
- package/dist/__tests__/group.test.js +106 -0
- package/dist/__tests__/matchers.d.ts +60 -0
- package/dist/__tests__/matchers.js +177 -0
- package/dist/__tests__/mock.d.ts +84 -0
- package/dist/__tests__/mock.js +181 -0
- package/dist/__tests__/pool.test.d.ts +1 -0
- package/dist/__tests__/pool.test.js +96 -0
- package/dist/__tests__/relay.test.d.ts +1 -0
- package/dist/__tests__/relay.test.js +654 -0
- package/dist/__tests__/spy.d.ts +7 -0
- package/dist/__tests__/spy.js +65 -0
- package/dist/__tests__/utils.d.ts +3 -0
- package/dist/__tests__/utils.js +8 -0
- package/dist/operators/__tests__/exports.test.d.ts +1 -0
- package/dist/operators/__tests__/exports.test.js +15 -0
- package/dist/operators/__tests__/to-event-store.test.d.ts +1 -0
- package/dist/operators/__tests__/to-event-store.test.js +18 -0
- package/dist/relay.d.ts +16 -2
- package/dist/relay.js +83 -34
- package/dist/server/connection.d.ts +1 -0
- package/dist/server/connection.js +9 -0
- package/dist/types.d.ts +42 -40
- package/package.json +4 -4
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import * as exports from "../index.js";
|
|
3
|
+
describe("exports", () => {
|
|
4
|
+
it("should export the expected functions", () => {
|
|
5
|
+
expect(Object.keys(exports).sort()).toMatchInlineSnapshot(`
|
|
6
|
+
[
|
|
7
|
+
"completeOnEose",
|
|
8
|
+
"markFromRelay",
|
|
9
|
+
"onlyEvents",
|
|
10
|
+
"storeEvents",
|
|
11
|
+
"toEventStore",
|
|
12
|
+
]
|
|
13
|
+
`);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EventStore } from "applesauce-core";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { FakeUser } from "../../__tests__/fake-user.js";
|
|
4
|
+
import { of } from "rxjs";
|
|
5
|
+
import { subscribeSpyTo } from "@hirez_io/observer-spy";
|
|
6
|
+
import { toEventStore } from "../to-event-store.js";
|
|
7
|
+
const user = new FakeUser();
|
|
8
|
+
describe("toEventStore", () => {
|
|
9
|
+
it("should remove duplicate events", () => {
|
|
10
|
+
const eventStore = new EventStore();
|
|
11
|
+
const event = user.note("original content");
|
|
12
|
+
const source = of(event, { ...event }, { ...event }, { ...event });
|
|
13
|
+
const spy = subscribeSpyTo(source.pipe(toEventStore(eventStore)));
|
|
14
|
+
expect(spy.getValuesLength()).toBe(1);
|
|
15
|
+
expect(spy.getValueAt(0).length).toBe(1);
|
|
16
|
+
expect(spy.getValueAt(0)[0]).toBe(event);
|
|
17
|
+
});
|
|
18
|
+
});
|
package/dist/relay.d.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { logger } from "applesauce-core";
|
|
2
2
|
import { type Filter, type NostrEvent } from "nostr-tools";
|
|
3
|
-
import { BehaviorSubject, Observable } from "rxjs";
|
|
4
|
-
import { WebSocketSubject, WebSocketSubjectConfig } from "rxjs/webSocket";
|
|
5
3
|
import { RelayInformation } from "nostr-tools/nip11";
|
|
4
|
+
import { BehaviorSubject, MonoTypeOperatorFunction, Observable, RepeatConfig, RetryConfig, Subject } from "rxjs";
|
|
5
|
+
import { WebSocketSubject, WebSocketSubjectConfig } from "rxjs/webSocket";
|
|
6
6
|
import { AuthSigner, FilterInput, IRelay, PublishOptions, PublishResponse, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
|
|
7
7
|
/** An error that is thrown when a REQ is closed from the relay side */
|
|
8
8
|
export declare class ReqCloseError extends Error {
|
|
9
9
|
}
|
|
10
10
|
export type RelayOptions = {
|
|
11
|
+
/** Custom WebSocket implementation */
|
|
11
12
|
WebSocket?: WebSocketSubjectConfig<any>["WebSocketCtor"];
|
|
13
|
+
/** How long to wait for an EOSE message (default 10s) */
|
|
12
14
|
eoseTimeout?: number;
|
|
15
|
+
/** How long to wait for an OK message from the relay (default 10s) */
|
|
13
16
|
eventTimeout?: number;
|
|
17
|
+
/** How long to keep the connection alive after nothing is subscribed (default 30s) */
|
|
14
18
|
keepAlive?: number;
|
|
15
19
|
};
|
|
16
20
|
export declare class Relay implements IRelay {
|
|
@@ -50,6 +54,12 @@ export declare class Relay implements IRelay {
|
|
|
50
54
|
protected _nip11: RelayInformation | null;
|
|
51
55
|
/** An observable that emits the limitations for the relay */
|
|
52
56
|
limitations$: Observable<RelayInformation["limitation"] | null>;
|
|
57
|
+
/** An observable that emits when underlying websocket is opened */
|
|
58
|
+
open$: Subject<Event>;
|
|
59
|
+
/** An observable that emits when underlying websocket is closed */
|
|
60
|
+
close$: Subject<CloseEvent>;
|
|
61
|
+
/** An observable that emits when underlying websocket is closing due to unsubscription */
|
|
62
|
+
closing$: Subject<void>;
|
|
53
63
|
get connected(): boolean;
|
|
54
64
|
get challenge(): string | null;
|
|
55
65
|
get notices(): string[];
|
|
@@ -87,6 +97,10 @@ export declare class Relay implements IRelay {
|
|
|
87
97
|
auth(event: NostrEvent): Promise<PublishResponse>;
|
|
88
98
|
/** Authenticate with the relay using a signer */
|
|
89
99
|
authenticate(signer: AuthSigner): Promise<PublishResponse>;
|
|
100
|
+
/** Internal operator for creating the retry() operator */
|
|
101
|
+
protected customRetryOperator<T extends unknown = unknown>(times: undefined | boolean | number | RetryConfig, base?: RetryConfig): MonoTypeOperatorFunction<T>;
|
|
102
|
+
/** Internal operator for creating the repeat() operator */
|
|
103
|
+
protected customRepeatOperator<T extends unknown = unknown>(times: undefined | boolean | number | RepeatConfig | undefined): MonoTypeOperatorFunction<T>;
|
|
90
104
|
/** Creates a REQ that retries when relay errors ( default 3 retries ) */
|
|
91
105
|
subscription(filters: Filter | Filter[], opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
|
|
92
106
|
/** Makes a single request that retires on errors and completes on EOSE */
|
package/dist/relay.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { logger } from "applesauce-core";
|
|
2
|
+
import { ensureHttpURL } from "applesauce-core/helpers";
|
|
2
3
|
import { simpleTimeout } from "applesauce-core/observable";
|
|
3
4
|
import { nanoid } from "nanoid";
|
|
4
5
|
import { nip42 } from "nostr-tools";
|
|
5
|
-
import { BehaviorSubject, catchError, combineLatest, defer, endWith, filter, finalize, from, ignoreElements, isObservable, lastValueFrom, map, merge, mergeMap, mergeWith, NEVER, of, retry, scan, share, shareReplay, Subject, switchMap, take, takeUntil, tap, throwError, timeout, timer, } from "rxjs";
|
|
6
|
+
import { BehaviorSubject, catchError, combineLatest, defer, endWith, filter, finalize, from, identity, ignoreElements, isObservable, lastValueFrom, map, merge, mergeMap, mergeWith, NEVER, of, repeat, retry, scan, share, shareReplay, Subject, switchMap, take, takeUntil, tap, throwError, timeout, timer, } from "rxjs";
|
|
6
7
|
import { webSocket } from "rxjs/webSocket";
|
|
7
|
-
import { ensureHttpURL } from "applesauce-core/helpers";
|
|
8
8
|
import { completeOnEose } from "./operators/complete-on-eose.js";
|
|
9
9
|
import { markFromRelay } from "./operators/mark-from-relay.js";
|
|
10
|
+
const DEFAULT_RETRY_CONFIG = { count: 10, delay: 1000, resetOnSuccess: true };
|
|
10
11
|
/** An error that is thrown when a REQ is closed from the relay side */
|
|
11
12
|
export class ReqCloseError extends Error {
|
|
12
13
|
}
|
|
@@ -47,6 +48,12 @@ export class Relay {
|
|
|
47
48
|
_nip11 = null;
|
|
48
49
|
/** An observable that emits the limitations for the relay */
|
|
49
50
|
limitations$;
|
|
51
|
+
/** An observable that emits when underlying websocket is opened */
|
|
52
|
+
open$ = new Subject();
|
|
53
|
+
/** An observable that emits when underlying websocket is closed */
|
|
54
|
+
close$ = new Subject();
|
|
55
|
+
/** An observable that emits when underlying websocket is closing due to unsubscription */
|
|
56
|
+
closing$ = new Subject();
|
|
50
57
|
// sync state
|
|
51
58
|
get connected() {
|
|
52
59
|
return this.connected$.value;
|
|
@@ -107,28 +114,28 @@ export class Relay {
|
|
|
107
114
|
this.authenticated$ = this.authenticationResponse$.pipe(map((response) => response?.ok === true));
|
|
108
115
|
/** Use the static method to create a new reconnect method for this relay */
|
|
109
116
|
this.reconnectTimer = Relay.createReconnectTimer(url);
|
|
117
|
+
// Subscribe to open and close events
|
|
118
|
+
this.open$.subscribe(() => {
|
|
119
|
+
this.log("Connected");
|
|
120
|
+
this.connected$.next(true);
|
|
121
|
+
this.attempts$.next(0);
|
|
122
|
+
this.error$.next(null);
|
|
123
|
+
this.resetState();
|
|
124
|
+
});
|
|
125
|
+
this.close$.subscribe((event) => {
|
|
126
|
+
this.log("Disconnected");
|
|
127
|
+
this.connected$.next(false);
|
|
128
|
+
this.attempts$.next(this.attempts$.value + 1);
|
|
129
|
+
this.resetState();
|
|
130
|
+
// Start the reconnect timer if the connection was not closed cleanly
|
|
131
|
+
if (!event.wasClean)
|
|
132
|
+
this.startReconnectTimer(event);
|
|
133
|
+
});
|
|
110
134
|
this.socket = webSocket({
|
|
111
135
|
url,
|
|
112
|
-
openObserver:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.connected$.next(true);
|
|
116
|
-
this.attempts$.next(0);
|
|
117
|
-
this.error$.next(null);
|
|
118
|
-
this.resetState();
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
closeObserver: {
|
|
122
|
-
next: (event) => {
|
|
123
|
-
this.log("Disconnected");
|
|
124
|
-
this.connected$.next(false);
|
|
125
|
-
this.attempts$.next(this.attempts$.value + 1);
|
|
126
|
-
this.resetState();
|
|
127
|
-
// Start the reconnect timer if the connection was not closed cleanly
|
|
128
|
-
if (!event.wasClean)
|
|
129
|
-
this.startReconnectTimer(event);
|
|
130
|
-
},
|
|
131
|
-
},
|
|
136
|
+
openObserver: this.open$,
|
|
137
|
+
closeObserver: this.close$,
|
|
138
|
+
closingObserver: this.closing$,
|
|
132
139
|
WebSocketCtor: opts?.WebSocket,
|
|
133
140
|
});
|
|
134
141
|
// Create an observable to fetch the NIP-11 information document
|
|
@@ -207,7 +214,7 @@ export class Relay {
|
|
|
207
214
|
}
|
|
208
215
|
/** Wait for authentication state, make connection and then wait for authentication if required */
|
|
209
216
|
waitForAuth(
|
|
210
|
-
// NOTE: require BehaviorSubject so it always has a value
|
|
217
|
+
// NOTE: require BehaviorSubject or shareReplay so it always has a value
|
|
211
218
|
requireAuth, observable) {
|
|
212
219
|
return combineLatest([requireAuth, this.authenticated$]).pipe(
|
|
213
220
|
// Once the auth state is known, make a connection and watch for auth challenges
|
|
@@ -245,21 +252,26 @@ export class Relay {
|
|
|
245
252
|
// Convert filters input into an observable, if its a normal value merge it with NEVER so it never completes
|
|
246
253
|
const input = isObservable(filters) ? filters : merge(of(filters), NEVER);
|
|
247
254
|
// Create an observable that completes when the upstream observable completes
|
|
248
|
-
const
|
|
255
|
+
const filtersComplete = input.pipe(ignoreElements(), endWith(null));
|
|
249
256
|
// Create an observable that filters responses from the relay to just the ones for this REQ
|
|
250
|
-
const messages = this.socket.pipe(filter((m) => Array.isArray(m) && (m[0] === "EVENT" || m[0] === "CLOSED" || m[0] === "EOSE") && m[1] === id)
|
|
257
|
+
const messages = this.socket.pipe(filter((m) => Array.isArray(m) && (m[0] === "EVENT" || m[0] === "CLOSED" || m[0] === "EOSE") && m[1] === id),
|
|
258
|
+
// Singleton (prevents the .pipe() operator later from sending two REQ messages )
|
|
259
|
+
share());
|
|
251
260
|
// Create an observable that controls sending the filters and closing the REQ
|
|
252
261
|
const control = input.pipe(
|
|
253
262
|
// Send the filters when they change
|
|
254
263
|
tap((filters) => this.socket.next(Array.isArray(filters) ? ["REQ", id, ...filters] : ["REQ", id, filters])),
|
|
255
|
-
//
|
|
264
|
+
// Send the CLOSE message when unsubscribed or input completes
|
|
256
265
|
finalize(() => this.socket.next(["CLOSE", id])),
|
|
257
266
|
// Once filters have been sent, switch to listening for messages
|
|
258
267
|
switchMap(() => messages));
|
|
259
268
|
// Start the watch tower with the observables
|
|
260
269
|
const observable = merge(this.watchTower, control).pipe(
|
|
270
|
+
// Complete the subscription when the control observable completes
|
|
271
|
+
// This is to work around the fact that merge() waits for both observables to complete
|
|
272
|
+
takeUntil(messages.pipe(ignoreElements(), endWith(true))),
|
|
261
273
|
// Complete the subscription when the input is completed
|
|
262
|
-
takeUntil(
|
|
274
|
+
takeUntil(filtersComplete),
|
|
263
275
|
// Map the messages to events, EOSE, or throw an error
|
|
264
276
|
map((message) => {
|
|
265
277
|
if (message[0] === "EOSE")
|
|
@@ -294,15 +306,20 @@ export class Relay {
|
|
|
294
306
|
}
|
|
295
307
|
/** Send an EVENT or AUTH message and return an observable of PublishResponse that completes or errors */
|
|
296
308
|
event(event, verb = "EVENT") {
|
|
297
|
-
const
|
|
309
|
+
const messages = defer(() => {
|
|
298
310
|
// Send event when subscription starts
|
|
299
311
|
this.socket.next([verb, event]);
|
|
300
312
|
return this.socket.pipe(filter((m) => m[0] === "OK" && m[1] === event.id),
|
|
301
313
|
// format OK message
|
|
302
314
|
map((m) => ({ ok: m[2], message: m[3], from: this.url })));
|
|
303
|
-
})
|
|
315
|
+
}).pipe(
|
|
316
|
+
// Singleton (prevents the .pipe() operator later from sending two EVENT messages )
|
|
317
|
+
share());
|
|
304
318
|
// Start the watch tower and add complete operators
|
|
305
|
-
const observable = merge(this.watchTower,
|
|
319
|
+
const observable = merge(this.watchTower, messages).pipe(
|
|
320
|
+
// Complete the subscription when the messages observable completes
|
|
321
|
+
// This is to work around the fact that merge() waits for both observables to complete
|
|
322
|
+
takeUntil(messages.pipe(ignoreElements(), endWith(true))),
|
|
306
323
|
// complete on first value
|
|
307
324
|
take(1),
|
|
308
325
|
// listen for OK auth-required
|
|
@@ -339,19 +356,49 @@ export class Relay {
|
|
|
339
356
|
const start = p instanceof Promise ? from(p) : of(p);
|
|
340
357
|
return lastValueFrom(start.pipe(switchMap((event) => this.auth(event))));
|
|
341
358
|
}
|
|
359
|
+
/** Internal operator for creating the retry() operator */
|
|
360
|
+
customRetryOperator(times, base) {
|
|
361
|
+
if (times === false)
|
|
362
|
+
return identity;
|
|
363
|
+
else if (typeof times === "number")
|
|
364
|
+
return retry({ ...base, count: times });
|
|
365
|
+
else if (times === true)
|
|
366
|
+
return base ? retry(base) : retry();
|
|
367
|
+
else
|
|
368
|
+
return retry({ ...base, ...times });
|
|
369
|
+
}
|
|
370
|
+
/** Internal operator for creating the repeat() operator */
|
|
371
|
+
customRepeatOperator(times) {
|
|
372
|
+
if (times === false || times === undefined)
|
|
373
|
+
return identity;
|
|
374
|
+
else if (times === true)
|
|
375
|
+
return repeat();
|
|
376
|
+
else if (typeof times === "number")
|
|
377
|
+
return repeat(times);
|
|
378
|
+
else
|
|
379
|
+
return repeat(times);
|
|
380
|
+
}
|
|
342
381
|
/** Creates a REQ that retries when relay errors ( default 3 retries ) */
|
|
343
382
|
subscription(filters, opts) {
|
|
344
383
|
return this.req(filters, opts?.id).pipe(
|
|
345
384
|
// Retry on connection errors
|
|
346
|
-
|
|
385
|
+
this.customRetryOperator(opts?.retries ?? opts?.reconnect ?? true, DEFAULT_RETRY_CONFIG),
|
|
386
|
+
// Create resubscribe logic (repeat operator)
|
|
387
|
+
this.customRepeatOperator(opts?.resubscribe),
|
|
388
|
+
// Single subscription
|
|
389
|
+
share());
|
|
347
390
|
}
|
|
348
391
|
/** Makes a single request that retires on errors and completes on EOSE */
|
|
349
392
|
request(filters, opts) {
|
|
350
393
|
return this.req(filters, opts?.id).pipe(
|
|
351
394
|
// Retry on connection errors
|
|
352
|
-
|
|
395
|
+
this.customRetryOperator(opts?.retries ?? opts?.reconnect ?? true, DEFAULT_RETRY_CONFIG),
|
|
396
|
+
// Create resubscribe logic (repeat operator)
|
|
397
|
+
this.customRepeatOperator(opts?.resubscribe),
|
|
353
398
|
// Complete when EOSE is received
|
|
354
|
-
completeOnEose()
|
|
399
|
+
completeOnEose(),
|
|
400
|
+
// Single subscription
|
|
401
|
+
share());
|
|
355
402
|
}
|
|
356
403
|
/** Publishes an event to the relay and retries when relay errors or responds with auth-required ( default 3 retries ) */
|
|
357
404
|
publish(event, opts) {
|
|
@@ -362,7 +409,9 @@ export class Relay {
|
|
|
362
409
|
return of(result);
|
|
363
410
|
}),
|
|
364
411
|
// Retry the publish until it succeeds or the number of retries is reached
|
|
365
|
-
|
|
412
|
+
this.customRetryOperator(opts?.retries ?? opts?.reconnect ?? true, DEFAULT_RETRY_CONFIG),
|
|
413
|
+
// Single subscription
|
|
414
|
+
share()));
|
|
366
415
|
}
|
|
367
416
|
/** Force close the connection */
|
|
368
417
|
close() {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
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()]);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type EventTemplate, type Filter, type NostrEvent } from "nostr-tools";
|
|
2
|
-
import { Observable } from "rxjs";
|
|
2
|
+
import { Observable, repeat, retry } from "rxjs";
|
|
3
3
|
import { WebSocketSubject } from "rxjs/webSocket";
|
|
4
4
|
export type SubscriptionResponse = NostrEvent | "EOSE";
|
|
5
5
|
export type PublishResponse = {
|
|
@@ -8,16 +8,42 @@ export type PublishResponse = {
|
|
|
8
8
|
from: string;
|
|
9
9
|
};
|
|
10
10
|
export type MultiplexWebSocket<T = any> = Pick<WebSocketSubject<T>, "multiplex">;
|
|
11
|
+
/** Options for the publish method on the pool and relay */
|
|
11
12
|
export type PublishOptions = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Number of times to retry the publish. default is 10
|
|
15
|
+
* @see https://rxjs.dev/api/index/function/retry
|
|
16
|
+
* @deprecated use `reconnect` instead
|
|
17
|
+
*/
|
|
18
|
+
retries?: number | Parameters<typeof retry>[0];
|
|
19
|
+
/**
|
|
20
|
+
* Whether to reconnect when socket fails to connect. default is true (10 retries with 1 second delay)
|
|
21
|
+
* @see https://rxjs.dev/api/index/function/retry
|
|
22
|
+
*/
|
|
23
|
+
reconnect?: boolean | number | Parameters<typeof retry>[0];
|
|
17
24
|
};
|
|
25
|
+
/** Options for the request method on the pool and relay */
|
|
26
|
+
export type RequestOptions = SubscriptionOptions;
|
|
27
|
+
/** Options for the subscription method on the pool and relay */
|
|
18
28
|
export type SubscriptionOptions = {
|
|
29
|
+
/** Custom REQ id for the subscription */
|
|
19
30
|
id?: string;
|
|
20
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Number of times to retry the subscription if the relay fails to connect. default is 10
|
|
33
|
+
* @see https://rxjs.dev/api/index/function/retry
|
|
34
|
+
* @deprecated use `reconnect` instead
|
|
35
|
+
*/
|
|
36
|
+
retries?: number | Parameters<typeof retry>[0];
|
|
37
|
+
/**
|
|
38
|
+
* Whether to resubscribe if the subscription is closed by the relay. default is false
|
|
39
|
+
* @see https://rxjs.dev/api/index/function/repeat
|
|
40
|
+
*/
|
|
41
|
+
resubscribe?: boolean | number | Parameters<typeof repeat>[0];
|
|
42
|
+
/**
|
|
43
|
+
* Whether to reconnect when socket is closed. default is true (10 retries with 1 second delay)
|
|
44
|
+
* @see https://rxjs.dev/api/index/function/retry
|
|
45
|
+
*/
|
|
46
|
+
reconnect?: boolean | number | Parameters<typeof retry>[0];
|
|
21
47
|
};
|
|
22
48
|
export type AuthSigner = {
|
|
23
49
|
signEvent: (event: EventTemplate) => NostrEvent | Promise<NostrEvent>;
|
|
@@ -47,19 +73,11 @@ export interface IRelay extends MultiplexWebSocket {
|
|
|
47
73
|
/** Authenticate with the relay using a signer */
|
|
48
74
|
authenticate(signer: AuthSigner): Promise<PublishResponse>;
|
|
49
75
|
/** Send an EVENT message with retries */
|
|
50
|
-
publish(event: NostrEvent, opts?:
|
|
51
|
-
retries?: number;
|
|
52
|
-
}): Promise<PublishResponse>;
|
|
76
|
+
publish(event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse>;
|
|
53
77
|
/** Send a REQ message with retries */
|
|
54
|
-
request(filters: FilterInput, opts?:
|
|
55
|
-
id?: string;
|
|
56
|
-
retries?: number;
|
|
57
|
-
}): Observable<NostrEvent>;
|
|
78
|
+
request(filters: FilterInput, opts?: RequestOptions): Observable<NostrEvent>;
|
|
58
79
|
/** Open a subscription with retries */
|
|
59
|
-
subscription(filters: FilterInput, opts?:
|
|
60
|
-
id?: string;
|
|
61
|
-
retries?: number;
|
|
62
|
-
}): Observable<SubscriptionResponse>;
|
|
80
|
+
subscription(filters: FilterInput, opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
|
|
63
81
|
}
|
|
64
82
|
export interface IGroup {
|
|
65
83
|
/** Send a REQ message */
|
|
@@ -67,19 +85,11 @@ export interface IGroup {
|
|
|
67
85
|
/** Send an EVENT message */
|
|
68
86
|
event(event: NostrEvent): Observable<PublishResponse>;
|
|
69
87
|
/** Send an EVENT message with retries */
|
|
70
|
-
publish(event: NostrEvent, opts?:
|
|
71
|
-
retries?: number;
|
|
72
|
-
}): Promise<PublishResponse[]>;
|
|
88
|
+
publish(event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse[]>;
|
|
73
89
|
/** Send a REQ message with retries */
|
|
74
|
-
request(filters: FilterInput, opts?:
|
|
75
|
-
id?: string;
|
|
76
|
-
retries?: number;
|
|
77
|
-
}): Observable<NostrEvent>;
|
|
90
|
+
request(filters: FilterInput, opts?: RequestOptions): Observable<NostrEvent>;
|
|
78
91
|
/** Open a subscription with retries */
|
|
79
|
-
subscription(filters: FilterInput, opts?:
|
|
80
|
-
id?: string;
|
|
81
|
-
retries?: number;
|
|
82
|
-
}): Observable<SubscriptionResponse>;
|
|
92
|
+
subscription(filters: FilterInput, opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
|
|
83
93
|
}
|
|
84
94
|
export interface IPool {
|
|
85
95
|
/** Get or create a relay */
|
|
@@ -93,17 +103,9 @@ export interface IPool {
|
|
|
93
103
|
/** Send an EVENT message */
|
|
94
104
|
event(relays: string[], event: NostrEvent): Observable<PublishResponse>;
|
|
95
105
|
/** Send an EVENT message to relays with retries */
|
|
96
|
-
publish(relays: string[], event: NostrEvent, opts?:
|
|
97
|
-
retries?: number;
|
|
98
|
-
}): Promise<PublishResponse[]>;
|
|
106
|
+
publish(relays: string[], event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse[]>;
|
|
99
107
|
/** Send a REQ message to relays with retries */
|
|
100
|
-
request(relays: string[], filters: FilterInput, opts?:
|
|
101
|
-
id?: string;
|
|
102
|
-
retries?: number;
|
|
103
|
-
}): Observable<NostrEvent>;
|
|
108
|
+
request(relays: string[], filters: FilterInput, opts?: RequestOptions): Observable<NostrEvent>;
|
|
104
109
|
/** Open a subscription to relays with retries */
|
|
105
|
-
subscription(relays: string[], filters: FilterInput, opts?:
|
|
106
|
-
id?: string;
|
|
107
|
-
retries?: number;
|
|
108
|
-
}): Observable<SubscriptionResponse>;
|
|
110
|
+
subscription(relays: string[], filters: FilterInput, opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
|
|
109
111
|
}
|
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-20250828144630",
|
|
4
4
|
"description": "nostr relay communication framework built on rxjs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -54,14 +54,14 @@
|
|
|
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-20250828144630",
|
|
58
58
|
"nanoid": "^5.0.9",
|
|
59
|
-
"nostr-tools": "
|
|
59
|
+
"nostr-tools": "~2.15",
|
|
60
60
|
"rxjs": "^7.8.1"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@hirez_io/observer-spy": "^2.2.0",
|
|
64
|
-
"applesauce-signers": "0.0.0-next-
|
|
64
|
+
"applesauce-signers": "0.0.0-next-20250828144630",
|
|
65
65
|
"@vitest/expect": "^3.1.1",
|
|
66
66
|
"typescript": "^5.7.3",
|
|
67
67
|
"vitest": "^3.2.3",
|