applesauce-relay 4.0.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/group.d.ts +24 -8
- package/dist/group.js +140 -38
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/liveness.d.ts +123 -0
- package/dist/liveness.js +327 -0
- package/dist/negentropy.js +41 -8
- package/dist/operators/index.d.ts +2 -0
- package/dist/operators/index.js +2 -0
- package/dist/operators/liveness.d.ts +17 -0
- package/dist/operators/liveness.js +47 -0
- package/dist/operators/reverse-switch-map.d.ts +9 -0
- package/dist/operators/reverse-switch-map.js +46 -0
- package/dist/pool.d.ts +16 -14
- package/dist/pool.js +33 -38
- package/dist/relay.d.ts +7 -3
- package/dist/relay.js +78 -19
- package/dist/types.d.ts +35 -15
- package/package.json +3 -3
package/dist/pool.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import { normalizeURL } from "applesauce-core/helpers";
|
|
2
|
-
import { BehaviorSubject, Subject } from "rxjs";
|
|
1
|
+
import { createFilterMap, isFilterEqual, normalizeURL } from "applesauce-core/helpers";
|
|
2
|
+
import { BehaviorSubject, distinctUntilChanged, isObservable, map, of, Subject } from "rxjs";
|
|
3
3
|
import { RelayGroup } from "./group.js";
|
|
4
4
|
import { Relay } from "./relay.js";
|
|
5
5
|
export class RelayPool {
|
|
6
6
|
options;
|
|
7
|
-
groups$ = new BehaviorSubject(new Map());
|
|
8
|
-
get groups() {
|
|
9
|
-
return this.groups$.value;
|
|
10
|
-
}
|
|
11
7
|
relays$ = new BehaviorSubject(new Map());
|
|
12
8
|
get relays() {
|
|
13
9
|
return this.relays$.value;
|
|
@@ -16,34 +12,13 @@ export class RelayPool {
|
|
|
16
12
|
add$ = new Subject();
|
|
17
13
|
/** A signal when a relay is removed */
|
|
18
14
|
remove$ = new Subject();
|
|
19
|
-
/** An array of relays to never connect to */
|
|
20
|
-
blacklist = new Set();
|
|
21
15
|
constructor(options) {
|
|
22
16
|
this.options = options;
|
|
23
|
-
// Listen for relays being added and removed to emit connect / disconnect signals
|
|
24
|
-
// const listeners = new Map<IRelay, Subscription>();
|
|
25
|
-
// this.add$.subscribe((relay) =>
|
|
26
|
-
// listeners.set(
|
|
27
|
-
// relay,
|
|
28
|
-
// relay.connected$.subscribe((conn) => (conn ? this.connect$.next(relay) : this.disconnect$.next(relay))),
|
|
29
|
-
// ),
|
|
30
|
-
// );
|
|
31
|
-
// this.remove$.subscribe((relay) => {
|
|
32
|
-
// const listener = listeners.get(relay);
|
|
33
|
-
// if (listener) listener.unsubscribe();
|
|
34
|
-
// listeners.delete(relay);
|
|
35
|
-
// });
|
|
36
|
-
}
|
|
37
|
-
filterBlacklist(urls) {
|
|
38
|
-
return urls.filter((url) => !this.blacklist.has(url));
|
|
39
17
|
}
|
|
40
18
|
/** Get or create a new relay connection */
|
|
41
19
|
relay(url) {
|
|
42
20
|
// Normalize the url
|
|
43
21
|
url = normalizeURL(url);
|
|
44
|
-
// Check if the url is blacklisted
|
|
45
|
-
if (this.blacklist.has(url))
|
|
46
|
-
throw new Error("Relay is on blacklist");
|
|
47
22
|
// Check if the relay already exists
|
|
48
23
|
let relay = this.relays.get(url);
|
|
49
24
|
if (relay)
|
|
@@ -57,17 +32,9 @@ export class RelayPool {
|
|
|
57
32
|
}
|
|
58
33
|
/** Create a group of relays */
|
|
59
34
|
group(relays) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
relays = this.filterBlacklist(relays);
|
|
64
|
-
const key = relays.sort().join(",");
|
|
65
|
-
let group = this.groups.get(key);
|
|
66
|
-
if (group)
|
|
67
|
-
return group;
|
|
68
|
-
group = new RelayGroup(relays.map((url) => this.relay(url)));
|
|
69
|
-
this.groups$.next(this.groups.set(key, group));
|
|
70
|
-
return group;
|
|
35
|
+
return new RelayGroup(Array.isArray(relays)
|
|
36
|
+
? relays.map((url) => this.relay(url))
|
|
37
|
+
: relays.pipe(map((urls) => urls.map((url) => this.relay(url)))));
|
|
71
38
|
}
|
|
72
39
|
/** Removes a relay from the pool and defaults to closing the connection */
|
|
73
40
|
remove(relay, close = true) {
|
|
@@ -112,6 +79,34 @@ export class RelayPool {
|
|
|
112
79
|
subscription(relays, filters, options) {
|
|
113
80
|
return this.group(relays).subscription(filters, options);
|
|
114
81
|
}
|
|
82
|
+
/** Open a subscription for a map of relays and filters */
|
|
83
|
+
subscriptionMap(relays, options) {
|
|
84
|
+
// Convert input to observable
|
|
85
|
+
const relays$ = isObservable(relays) ? relays : of(relays);
|
|
86
|
+
return this.group(
|
|
87
|
+
// Create a group with an observable of dynamic relay urls
|
|
88
|
+
relays$.pipe(map((dir) => Object.keys(dir)))).subscription((relay) => {
|
|
89
|
+
// Return observable to subscribe to the relays unique filters
|
|
90
|
+
return relays$.pipe(
|
|
91
|
+
// Select the relays filters
|
|
92
|
+
map((dir) => dir[relay.url]),
|
|
93
|
+
// Don't send duplicate filters
|
|
94
|
+
distinctUntilChanged(isFilterEqual));
|
|
95
|
+
}, options);
|
|
96
|
+
}
|
|
97
|
+
/** Open a subscription for an {@link OutboxMap} and filter */
|
|
98
|
+
outboxSubscription(outboxes, filter, options) {
|
|
99
|
+
const filterMap = isObservable(outboxes)
|
|
100
|
+
? outboxes.pipe(
|
|
101
|
+
// Project outbox map to filter map
|
|
102
|
+
map((outboxes) => createFilterMap(outboxes, filter)))
|
|
103
|
+
: createFilterMap(outboxes, filter);
|
|
104
|
+
return this.subscriptionMap(filterMap, options);
|
|
105
|
+
}
|
|
106
|
+
/** Count events on multiple relays */
|
|
107
|
+
count(relays, filters, id) {
|
|
108
|
+
return this.group(relays).count(filters, id);
|
|
109
|
+
}
|
|
115
110
|
/** Negentropy sync events with the relays and an event store */
|
|
116
111
|
sync(relays, store, filter, direction) {
|
|
117
112
|
return this.group(relays).sync(store, filter, direction);
|
package/dist/relay.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { RelayInformation } from "nostr-tools/nip11";
|
|
|
4
4
|
import { BehaviorSubject, MonoTypeOperatorFunction, Observable, RepeatConfig, RetryConfig, Subject } from "rxjs";
|
|
5
5
|
import { WebSocketSubject, WebSocketSubjectConfig } from "rxjs/webSocket";
|
|
6
6
|
import { type NegentropySyncOptions, type ReconcileFunction } from "./negentropy.js";
|
|
7
|
-
import { AuthSigner, FilterInput, IRelay, PublishOptions, PublishResponse, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
|
|
7
|
+
import { AuthSigner, CountResponse, FilterInput, IRelay, PublishOptions, PublishResponse, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
|
|
8
8
|
/** Flags for the negentropy sync type */
|
|
9
9
|
export declare enum SyncDirection {
|
|
10
10
|
RECEIVE = 1,
|
|
@@ -104,6 +104,8 @@ export declare class Relay implements IRelay {
|
|
|
104
104
|
send(message: any): void;
|
|
105
105
|
/** Create a REQ observable that emits events or "EOSE" or errors */
|
|
106
106
|
req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
|
|
107
|
+
/** Create a COUNT observable that emits a single count response */
|
|
108
|
+
count(filters: Filter | Filter[], id?: string): Observable<CountResponse>;
|
|
107
109
|
/** Send an EVENT or AUTH message and return an observable of PublishResponse that completes or errors */
|
|
108
110
|
event(event: NostrEvent, verb?: "EVENT" | "AUTH"): Observable<PublishResponse>;
|
|
109
111
|
/** send and AUTH message */
|
|
@@ -118,10 +120,12 @@ export declare class Relay implements IRelay {
|
|
|
118
120
|
protected customRepeatOperator<T extends unknown = unknown>(times: undefined | boolean | number | RepeatConfig | undefined): MonoTypeOperatorFunction<T>;
|
|
119
121
|
/** Internal operator for creating the timeout() operator */
|
|
120
122
|
protected customTimeoutOperator<T extends unknown = unknown>(timeout: undefined | boolean | number, defaultTimeout: number): MonoTypeOperatorFunction<T>;
|
|
123
|
+
/** Internal operator for handling auth-required errors from REQ/COUNT operations */
|
|
124
|
+
protected handleAuthRequiredForReq(operation: "REQ" | "COUNT"): MonoTypeOperatorFunction<any>;
|
|
121
125
|
/** Creates a REQ that retries when relay errors ( default 3 retries ) */
|
|
122
|
-
subscription(filters:
|
|
126
|
+
subscription(filters: FilterInput, opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
|
|
123
127
|
/** Makes a single request that retires on errors and completes on EOSE */
|
|
124
|
-
request(filters:
|
|
128
|
+
request(filters: FilterInput, opts?: RequestOptions): Observable<NostrEvent>;
|
|
125
129
|
/** Publishes an event to the relay and retries when relay errors or responds with auth-required ( default 3 retries ) */
|
|
126
130
|
publish(event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse>;
|
|
127
131
|
/** Negentropy sync events with the relay and an event store */
|
package/dist/relay.js
CHANGED
|
@@ -7,6 +7,7 @@ import { BehaviorSubject, catchError, combineLatest, defer, endWith, filter, fin
|
|
|
7
7
|
import { webSocket } from "rxjs/webSocket";
|
|
8
8
|
import { completeOnEose } from "./operators/complete-on-eose.js";
|
|
9
9
|
import { markFromRelay } from "./operators/mark-from-relay.js";
|
|
10
|
+
const AUTH_REQUIRED_PREFIX = "auth-required:";
|
|
10
11
|
const DEFAULT_RETRY_CONFIG = { count: 10, delay: 1000, resetOnSuccess: true };
|
|
11
12
|
/** Flags for the negentropy sync type */
|
|
12
13
|
export var SyncDirection;
|
|
@@ -264,7 +265,15 @@ export class Relay {
|
|
|
264
265
|
/** Create a REQ observable that emits events or "EOSE" or errors */
|
|
265
266
|
req(filters, id = nanoid()) {
|
|
266
267
|
// Convert filters input into an observable, if its a normal value merge it with NEVER so it never completes
|
|
267
|
-
|
|
268
|
+
let input;
|
|
269
|
+
// Create input from filters input
|
|
270
|
+
if (typeof filters === "function") {
|
|
271
|
+
const result = filters(this);
|
|
272
|
+
input = (isObservable(result) ? result : merge(of(result), NEVER)).pipe(map((f) => (Array.isArray(f) ? f : [f])));
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
input = (isObservable(filters) ? filters : merge(of(filters), NEVER)).pipe(map((f) => (Array.isArray(f) ? f : [f])));
|
|
276
|
+
}
|
|
268
277
|
// Create an observable that completes when the upstream observable completes
|
|
269
278
|
const filtersComplete = input.pipe(ignoreElements(), endWith(null));
|
|
270
279
|
// Create an observable that filters responses from the relay to just the ones for this REQ
|
|
@@ -274,7 +283,7 @@ export class Relay {
|
|
|
274
283
|
// Create an observable that controls sending the filters and closing the REQ
|
|
275
284
|
const control = input.pipe(
|
|
276
285
|
// Send the filters when they change
|
|
277
|
-
tap((filters) => this.socket.next(
|
|
286
|
+
tap((filters) => this.socket.next(["REQ", id, ...filters])),
|
|
278
287
|
// Send the CLOSE message when unsubscribed or input completes
|
|
279
288
|
finalize(() => this.socket.next(["CLOSE", id])),
|
|
280
289
|
// Once filters have been sent, switch to listening for messages
|
|
@@ -294,21 +303,11 @@ export class Relay {
|
|
|
294
303
|
throw new ReqCloseError(message[2]);
|
|
295
304
|
else
|
|
296
305
|
return message[2];
|
|
297
|
-
}),
|
|
298
|
-
// Set REQ auth required if the REQ is closed with auth-required
|
|
299
|
-
if (error instanceof ReqCloseError &&
|
|
300
|
-
error.message.startsWith("auth-required") &&
|
|
301
|
-
!this.receivedAuthRequiredForReq.value) {
|
|
302
|
-
this.log("Auth required for REQ");
|
|
303
|
-
this.receivedAuthRequiredForReq.next(true);
|
|
304
|
-
}
|
|
305
|
-
// Pass the error through
|
|
306
|
-
return throwError(() => error);
|
|
307
|
-
}),
|
|
306
|
+
}), this.handleAuthRequiredForReq("REQ"),
|
|
308
307
|
// mark events as from relays
|
|
309
308
|
markFromRelay(this.url),
|
|
310
309
|
// if no events are seen in 10s, emit EOSE
|
|
311
|
-
// TODO: this should emit EOSE
|
|
310
|
+
// TODO: this timeout should only emit EOSE after the last event is seen and no EOSE has been sent in (timeout ms)
|
|
312
311
|
timeout({
|
|
313
312
|
first: this.eoseTimeout,
|
|
314
313
|
with: () => merge(of("EOSE"), NEVER),
|
|
@@ -318,6 +317,38 @@ export class Relay {
|
|
|
318
317
|
// Wait for auth if required and make sure to start the watch tower
|
|
319
318
|
return this.waitForReady(this.waitForAuth(this.authRequiredForRead$, observable));
|
|
320
319
|
}
|
|
320
|
+
/** Create a COUNT observable that emits a single count response */
|
|
321
|
+
count(filters, id = nanoid()) {
|
|
322
|
+
// Create an observable that filters responses from the relay to just the ones for this COUNT
|
|
323
|
+
const messages = this.socket.pipe(filter((m) => Array.isArray(m) && (m[0] === "COUNT" || m[0] === "CLOSED") && m[1] === id),
|
|
324
|
+
// Singleton (prevents duplicate subscriptions)
|
|
325
|
+
share());
|
|
326
|
+
// Send the COUNT message and listen for response
|
|
327
|
+
const observable = defer(() => {
|
|
328
|
+
// Send the COUNT message when subscription starts
|
|
329
|
+
this.socket.next(Array.isArray(filters) ? ["COUNT", id, ...filters] : ["COUNT", id, filters]);
|
|
330
|
+
// Merge with watch tower to keep connection alive
|
|
331
|
+
return merge(this.watchTower, messages);
|
|
332
|
+
}).pipe(
|
|
333
|
+
// Map the messages to count responses or throw an error
|
|
334
|
+
map((message) => {
|
|
335
|
+
if (message[0] === "COUNT")
|
|
336
|
+
return message[2];
|
|
337
|
+
else
|
|
338
|
+
throw new ReqCloseError(message[2]);
|
|
339
|
+
}), this.handleAuthRequiredForReq("COUNT"),
|
|
340
|
+
// Complete on first value (COUNT responses are single-shot)
|
|
341
|
+
take(1),
|
|
342
|
+
// Add timeout
|
|
343
|
+
timeout({
|
|
344
|
+
first: this.eoseTimeout,
|
|
345
|
+
with: () => throwError(() => new Error("COUNT timeout")),
|
|
346
|
+
}),
|
|
347
|
+
// Only create one upstream subscription
|
|
348
|
+
share());
|
|
349
|
+
// Start the watch tower and wait for auth if required
|
|
350
|
+
return this.waitForReady(this.waitForAuth(this.authRequiredForRead$, observable));
|
|
351
|
+
}
|
|
321
352
|
/** Send an EVENT or AUTH message and return an observable of PublishResponse that completes or errors */
|
|
322
353
|
event(event, verb = "EVENT") {
|
|
323
354
|
const messages = defer(() => {
|
|
@@ -338,7 +369,7 @@ export class Relay {
|
|
|
338
369
|
take(1),
|
|
339
370
|
// listen for OK auth-required
|
|
340
371
|
tap(({ ok, message }) => {
|
|
341
|
-
if (ok === false && message?.startsWith(
|
|
372
|
+
if (ok === false && message?.startsWith(AUTH_REQUIRED_PREFIX) && !this.receivedAuthRequiredForEvent.value) {
|
|
342
373
|
this.log("Auth required for publish");
|
|
343
374
|
this.receivedAuthRequiredForEvent.next(true);
|
|
344
375
|
}
|
|
@@ -414,6 +445,20 @@ export class Relay {
|
|
|
414
445
|
else
|
|
415
446
|
return simpleTimeout(timeout ?? defaultTimeout);
|
|
416
447
|
}
|
|
448
|
+
/** Internal operator for handling auth-required errors from REQ/COUNT operations */
|
|
449
|
+
handleAuthRequiredForReq(operation) {
|
|
450
|
+
return catchError((error) => {
|
|
451
|
+
// Set auth required if the operation is closed with auth-required
|
|
452
|
+
if (error instanceof ReqCloseError &&
|
|
453
|
+
error.message.startsWith(AUTH_REQUIRED_PREFIX) &&
|
|
454
|
+
!this.receivedAuthRequiredForReq.value) {
|
|
455
|
+
this.log(`Auth required for ${operation}`);
|
|
456
|
+
this.receivedAuthRequiredForReq.next(true);
|
|
457
|
+
}
|
|
458
|
+
// Pass the error through
|
|
459
|
+
return throwError(() => error);
|
|
460
|
+
});
|
|
461
|
+
}
|
|
417
462
|
/** Creates a REQ that retries when relay errors ( default 3 retries ) */
|
|
418
463
|
subscription(filters, opts) {
|
|
419
464
|
return this.req(filters, opts?.id).pipe(
|
|
@@ -440,7 +485,7 @@ export class Relay {
|
|
|
440
485
|
publish(event, opts) {
|
|
441
486
|
return lastValueFrom(this.event(event).pipe(mergeMap((result) => {
|
|
442
487
|
// If the relay responds with auth-required, throw an error for the retry operator to handle
|
|
443
|
-
if (result.ok === false && result.message?.startsWith(
|
|
488
|
+
if (result.ok === false && result.message?.startsWith(AUTH_REQUIRED_PREFIX))
|
|
444
489
|
return throwError(() => new Error(result.message));
|
|
445
490
|
return of(result);
|
|
446
491
|
}),
|
|
@@ -461,6 +506,14 @@ export class Relay {
|
|
|
461
506
|
};
|
|
462
507
|
return new Observable((observer) => {
|
|
463
508
|
const controller = new AbortController();
|
|
509
|
+
let cleanupCalled = false;
|
|
510
|
+
// Store reference to cleanup the negentropy properly
|
|
511
|
+
const cleanup = () => {
|
|
512
|
+
if (!cleanupCalled) {
|
|
513
|
+
cleanupCalled = true;
|
|
514
|
+
controller.abort();
|
|
515
|
+
}
|
|
516
|
+
};
|
|
464
517
|
this.negentropy(store, filter, async (have, need) => {
|
|
465
518
|
// NOTE: it may be more efficient to sync all the events later in a single batch
|
|
466
519
|
// Send missing events to the relay
|
|
@@ -475,11 +528,17 @@ export class Relay {
|
|
|
475
528
|
}
|
|
476
529
|
}, { signal: controller.signal })
|
|
477
530
|
// Complete the observable when the sync is complete
|
|
478
|
-
.then(() =>
|
|
531
|
+
.then(() => {
|
|
532
|
+
if (!cleanupCalled)
|
|
533
|
+
observer.complete();
|
|
534
|
+
})
|
|
479
535
|
// Error the observable when the sync fails
|
|
480
|
-
.catch((err) =>
|
|
536
|
+
.catch((err) => {
|
|
537
|
+
if (!cleanupCalled)
|
|
538
|
+
observer.error(err);
|
|
539
|
+
});
|
|
481
540
|
// Cancel the sync when the observable is unsubscribed
|
|
482
|
-
return
|
|
541
|
+
return cleanup;
|
|
483
542
|
}).pipe(
|
|
484
543
|
// Only create one upstream subscription
|
|
485
544
|
share());
|
package/dist/types.d.ts
CHANGED
|
@@ -12,6 +12,9 @@ export type PublishResponse = {
|
|
|
12
12
|
message?: string;
|
|
13
13
|
from: string;
|
|
14
14
|
};
|
|
15
|
+
export type CountResponse = {
|
|
16
|
+
count: number;
|
|
17
|
+
};
|
|
15
18
|
export type MultiplexWebSocket<T = any> = Pick<WebSocketSubject<T>, "multiplex">;
|
|
16
19
|
/** Options for the publish method on the pool and relay */
|
|
17
20
|
export type PublishOptions = {
|
|
@@ -55,8 +58,8 @@ export type SubscriptionOptions = {
|
|
|
55
58
|
export type AuthSigner = {
|
|
56
59
|
signEvent: (event: EventTemplate) => NostrEvent | Promise<NostrEvent>;
|
|
57
60
|
};
|
|
58
|
-
/**
|
|
59
|
-
export type FilterInput = Filter | Filter[] | Observable<Filter | Filter[]
|
|
61
|
+
/** Filters that can be passed to request methods on the pool or relay */
|
|
62
|
+
export type FilterInput = Filter | Filter[] | Observable<Filter | Filter[]> | ((relay: IRelay) => Filter | Filter[] | Observable<Filter | Filter[]>);
|
|
60
63
|
export interface IRelay extends MultiplexWebSocket {
|
|
61
64
|
url: string;
|
|
62
65
|
message$: Observable<any>;
|
|
@@ -65,6 +68,9 @@ export interface IRelay extends MultiplexWebSocket {
|
|
|
65
68
|
challenge$: Observable<string | null>;
|
|
66
69
|
authenticated$: Observable<boolean>;
|
|
67
70
|
notices$: Observable<string[]>;
|
|
71
|
+
open$: Observable<Event>;
|
|
72
|
+
close$: Observable<CloseEvent>;
|
|
73
|
+
closing$: Observable<void>;
|
|
68
74
|
error$: Observable<Error | null>;
|
|
69
75
|
readonly connected: boolean;
|
|
70
76
|
readonly authenticated: boolean;
|
|
@@ -74,6 +80,8 @@ export interface IRelay extends MultiplexWebSocket {
|
|
|
74
80
|
close(): void;
|
|
75
81
|
/** Send a REQ message */
|
|
76
82
|
req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
|
|
83
|
+
/** Send a COUNT message */
|
|
84
|
+
count(filters: Filter | Filter[], id?: string): Observable<CountResponse>;
|
|
77
85
|
/** Send an EVENT message */
|
|
78
86
|
event(event: NostrEvent): Observable<PublishResponse>;
|
|
79
87
|
/** Send an AUTH message */
|
|
@@ -97,19 +105,28 @@ export interface IRelay extends MultiplexWebSocket {
|
|
|
97
105
|
/** Get the supported NIPs for the relay */
|
|
98
106
|
getSupported(): Promise<number[] | null>;
|
|
99
107
|
}
|
|
108
|
+
export type IGroupRelayInput = IRelay[] | Observable<IRelay[]>;
|
|
100
109
|
export interface IGroup {
|
|
101
110
|
/** Send a REQ message */
|
|
102
|
-
req(filters:
|
|
111
|
+
req(filters: Parameters<IRelay["req"]>[0], id?: string): Observable<SubscriptionResponse>;
|
|
103
112
|
/** Send an EVENT message */
|
|
104
|
-
event(event:
|
|
113
|
+
event(event: Parameters<IRelay["event"]>[0]): Observable<PublishResponse>;
|
|
105
114
|
/** Negentropy sync event ids with the relays and an event store */
|
|
106
115
|
negentropy(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
|
|
116
|
+
/** Add a relay to the group */
|
|
117
|
+
add(relay: IRelay): void;
|
|
118
|
+
/** Remove a relay from the group */
|
|
119
|
+
remove(relay: IRelay): void;
|
|
120
|
+
/** Check if a relay is in the group */
|
|
121
|
+
has(relay: IRelay | string): boolean;
|
|
107
122
|
/** Send an EVENT message with retries */
|
|
108
|
-
publish(event:
|
|
123
|
+
publish(event: Parameters<IRelay["event"]>[0], opts?: PublishOptions): Promise<PublishResponse[]>;
|
|
109
124
|
/** Send a REQ message with retries */
|
|
110
|
-
request(filters:
|
|
125
|
+
request(filters: Parameters<IRelay["request"]>[0], opts?: GroupRequestOptions): Observable<NostrEvent>;
|
|
111
126
|
/** Open a subscription with retries */
|
|
112
|
-
subscription(filters:
|
|
127
|
+
subscription(filters: Parameters<IRelay["subscription"]>[0], opts?: GroupSubscriptionOptions): Observable<SubscriptionResponse>;
|
|
128
|
+
/** Count events on the relays and an event store */
|
|
129
|
+
count(filters: Filter | Filter[], id?: string): Observable<Record<string, CountResponse>>;
|
|
113
130
|
/** Negentropy sync events with the relay and an event store */
|
|
114
131
|
sync(store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
|
|
115
132
|
}
|
|
@@ -118,25 +135,28 @@ export interface IPoolSignals {
|
|
|
118
135
|
add$: Observable<IRelay>;
|
|
119
136
|
remove$: Observable<IRelay>;
|
|
120
137
|
}
|
|
138
|
+
export type IPoolRelayInput = string[] | Observable<string[]>;
|
|
121
139
|
export interface IPool extends IPoolSignals {
|
|
122
140
|
/** Get or create a relay */
|
|
123
141
|
relay(url: string): IRelay;
|
|
124
142
|
/** Create a relay group */
|
|
125
|
-
group(relays:
|
|
143
|
+
group(relays: IPoolRelayInput): IGroup;
|
|
126
144
|
/** Removes a relay from the pool and defaults to closing the connection */
|
|
127
145
|
remove(relay: string | IRelay, close?: boolean): void;
|
|
128
146
|
/** Send a REQ message */
|
|
129
|
-
req(relays:
|
|
147
|
+
req(relays: IPoolRelayInput, filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
|
|
130
148
|
/** Send an EVENT message */
|
|
131
|
-
event(relays:
|
|
149
|
+
event(relays: IPoolRelayInput, event: NostrEvent): Observable<PublishResponse>;
|
|
132
150
|
/** Negentropy sync event ids with the relays and an event store */
|
|
133
|
-
negentropy(relays:
|
|
151
|
+
negentropy(relays: IPoolRelayInput, store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, reconcile: ReconcileFunction, opts?: GroupNegentropySyncOptions): Promise<boolean>;
|
|
134
152
|
/** Send an EVENT message to relays with retries */
|
|
135
|
-
publish(relays:
|
|
153
|
+
publish(relays: IPoolRelayInput, event: Parameters<IGroup["publish"]>[0], opts?: Parameters<IGroup["publish"]>[1]): Promise<PublishResponse[]>;
|
|
136
154
|
/** Send a REQ message to relays with retries */
|
|
137
|
-
request(relays:
|
|
155
|
+
request(relays: IPoolRelayInput, filters: Parameters<IGroup["request"]>[0], opts?: Parameters<IGroup["request"]>[1]): Observable<NostrEvent>;
|
|
138
156
|
/** Open a subscription to relays with retries */
|
|
139
|
-
subscription(relays:
|
|
157
|
+
subscription(relays: IPoolRelayInput, filters: Parameters<IGroup["subscription"]>[0], opts?: Parameters<IGroup["subscription"]>[1]): Observable<SubscriptionResponse>;
|
|
158
|
+
/** Count events on the relays and an event store */
|
|
159
|
+
count(relays: IPoolRelayInput, filters: Filter | Filter[], id?: string): Observable<Record<string, CountResponse>>;
|
|
140
160
|
/** Negentropy sync events with the relay and an event store */
|
|
141
|
-
sync(relays:
|
|
161
|
+
sync(relays: IPoolRelayInput, store: IEventStoreRead | IAsyncEventStoreRead | NostrEvent[], filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
|
|
142
162
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-relay",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "nostr relay communication framework built on rxjs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -52,14 +52,14 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@noble/hashes": "^1.7.1",
|
|
55
|
-
"applesauce-core": "^4.
|
|
55
|
+
"applesauce-core": "^4.2.0",
|
|
56
56
|
"nanoid": "^5.0.9",
|
|
57
57
|
"nostr-tools": "~2.17",
|
|
58
58
|
"rxjs": "^7.8.1"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@hirez_io/observer-spy": "^2.2.0",
|
|
62
|
-
"applesauce-signers": "^4.
|
|
62
|
+
"applesauce-signers": "^4.2.0",
|
|
63
63
|
"rimraf": "^6.0.1",
|
|
64
64
|
"typescript": "^5.7.3",
|
|
65
65
|
"vitest": "^3.2.4",
|