applesauce-relay 6.0.0 → 6.0.2
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/pool.js +1 -0
- package/dist/relay.d.ts +19 -12
- package/dist/relay.js +63 -19
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
package/dist/pool.js
CHANGED
package/dist/relay.d.ts
CHANGED
|
@@ -42,10 +42,10 @@ export type RelayOptions = {
|
|
|
42
42
|
now: number;
|
|
43
43
|
attempts: number;
|
|
44
44
|
}) => "reconnect" | "close" | "ignore";
|
|
45
|
-
/** Default
|
|
46
|
-
subscriptionReconnect?: RetryConfig;
|
|
47
|
-
/** Default
|
|
48
|
-
requestReconnect?: RetryConfig;
|
|
45
|
+
/** Default retry count or config for subscription() connection errors (default: 3) */
|
|
46
|
+
subscriptionReconnect?: number | RetryConfig;
|
|
47
|
+
/** Default retry count or config for request() connection errors (default: 3) */
|
|
48
|
+
requestReconnect?: number | RetryConfig;
|
|
49
49
|
/** Default retry config for publish() method */
|
|
50
50
|
publishRetry?: RetryConfig;
|
|
51
51
|
};
|
|
@@ -134,9 +134,9 @@ export declare class Relay {
|
|
|
134
134
|
pingFrequency: number;
|
|
135
135
|
/** How long to wait for EOSE response in milliseconds (default 20000) */
|
|
136
136
|
pingTimeout: number;
|
|
137
|
-
/** Default
|
|
137
|
+
/** Default retry config for subscription() connection errors */
|
|
138
138
|
subscriptionReconnect: RetryConfig;
|
|
139
|
-
/** Default
|
|
139
|
+
/** Default retry config for request() connection errors */
|
|
140
140
|
requestReconnect: RetryConfig;
|
|
141
141
|
/** Default retry config for publish() method */
|
|
142
142
|
publishRetry: RetryConfig;
|
|
@@ -159,7 +159,12 @@ export declare class Relay {
|
|
|
159
159
|
multiplex<T>(open: () => any, close: () => any, filter: (message: any) => boolean): Observable<T>;
|
|
160
160
|
/** Send a message to the relay */
|
|
161
161
|
send(message: any): void;
|
|
162
|
-
/**
|
|
162
|
+
/**
|
|
163
|
+
* Create a REQ observable that emits OPEN, EVENT, EOSE, and CLOSED messages.
|
|
164
|
+
*
|
|
165
|
+
* `resubscribe` only repeats after the relay sends a clean CLOSED message for this REQ.
|
|
166
|
+
* `reconnect` only retries connection errors and does not retry relay CLOSED errors.
|
|
167
|
+
*/
|
|
163
168
|
req(filters: FilterInput, opts?: RelayReqOptions): Observable<RelayReqMessage>;
|
|
164
169
|
/** Create a COUNT observable that emits a single count response */
|
|
165
170
|
count(filters: Filter | Filter[], id?: string): Observable<RelayCountResponse>;
|
|
@@ -171,15 +176,17 @@ export declare class Relay {
|
|
|
171
176
|
negentropy(store: NegentropyReadStore, filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
|
|
172
177
|
/** Authenticate with the relay using a signer */
|
|
173
178
|
authenticate(signer: AuthSigner): Promise<PublishResponse>;
|
|
174
|
-
/** Internal operator for creating
|
|
179
|
+
/** Internal operator for creating a retry() operator */
|
|
175
180
|
protected customRetryOperator<T extends unknown = unknown>(times: undefined | boolean | number | RetryConfig, base?: RetryConfig): MonoTypeOperatorFunction<T>;
|
|
176
|
-
/** Internal operator for
|
|
177
|
-
protected
|
|
181
|
+
/** Internal operator for retrying connection failures without retrying relay CLOSED errors */
|
|
182
|
+
protected customConnectionRetryOperator<T extends unknown = unknown>(times: undefined | boolean | number | RetryConfig, base?: RetryConfig): MonoTypeOperatorFunction<T>;
|
|
183
|
+
/** Internal operator for creating the repeat() operator, optionally gated by a condition */
|
|
184
|
+
protected customRepeatOperator<T extends unknown = unknown>(times: undefined | boolean | number | RepeatConfig | undefined, condition?: () => boolean): MonoTypeOperatorFunction<T>;
|
|
178
185
|
/** Internal operator for creating the timeout() operator */
|
|
179
186
|
protected customTimeoutOperator<T extends unknown = unknown>(timeout: undefined | boolean | number, defaultTimeout: number): MonoTypeOperatorFunction<T>;
|
|
180
|
-
/** Creates a REQ that retries
|
|
187
|
+
/** Creates a persistent REQ that retries connection errors (default 3 retries) */
|
|
181
188
|
subscription(filters: FilterInput, opts?: RelaySubscriptionOptions): Observable<RelaySubscriptionResponse>;
|
|
182
|
-
/** Makes a single request that
|
|
189
|
+
/** Makes a single request that retries connection errors and completes on EOSE */
|
|
183
190
|
request(filters: FilterInput, opts?: RelayRequestOptions): Observable<RelayRequestResponse>;
|
|
184
191
|
/** Publishes an event to the relay and retries when relay errors or responds with auth-required ( default 3 retries ) */
|
|
185
192
|
publish(event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse>;
|
package/dist/relay.js
CHANGED
|
@@ -4,16 +4,21 @@ import { ensureHttpURL } from "applesauce-core/helpers/url";
|
|
|
4
4
|
import { mapEventsToStore, simpleTimeout } from "applesauce-core/observable";
|
|
5
5
|
import { nanoid } from "nanoid";
|
|
6
6
|
import { makeAuthEvent } from "nostr-tools/nip42";
|
|
7
|
-
import { BehaviorSubject, catchError, combineLatest, defer, endWith, filter, finalize, firstValueFrom, from, identity, ignoreElements, isObservable, lastValueFrom, map, merge, mergeMap, mergeWith, NEVER, Observable, of, repeat, retry, scan, share, shareReplay, startWith, Subject, switchMap, take, takeUntil, takeWhile, tap, throwError, timeout, timer, } from "rxjs";
|
|
7
|
+
import { BehaviorSubject, catchError, combineLatest, defer, EMPTY, endWith, filter, finalize, firstValueFrom, from, identity, ignoreElements, isObservable, lastValueFrom, map, merge, mergeMap, mergeWith, NEVER, Observable, of, repeat, retry, scan, share, shareReplay, startWith, Subject, switchMap, take, takeUntil, takeWhile, tap, throwError, timeout, timer, } from "rxjs";
|
|
8
8
|
import { webSocket } from "rxjs/webSocket";
|
|
9
9
|
import { completeWhen } from "./operators/complete-when.js";
|
|
10
10
|
const AUTH_REQUIRED_PREFIX = "auth-required:";
|
|
11
|
-
/** Default retry
|
|
11
|
+
/** Default reconnect/retry config for request, subscription, and publish. linear backoff */
|
|
12
12
|
const DEFAULT_RETRY_CONFIG = {
|
|
13
13
|
count: 3,
|
|
14
14
|
delay: (_err, count) => timer(count * 1000),
|
|
15
15
|
resetOnSuccess: true,
|
|
16
16
|
};
|
|
17
|
+
function normalizeRetryConfig(config) {
|
|
18
|
+
if (typeof config === "number")
|
|
19
|
+
return { ...DEFAULT_RETRY_CONFIG, count: config };
|
|
20
|
+
return { ...DEFAULT_RETRY_CONFIG, ...(config ?? {}) };
|
|
21
|
+
}
|
|
17
22
|
/** Flags for the negentropy sync type */
|
|
18
23
|
export var SyncDirection;
|
|
19
24
|
(function (SyncDirection) {
|
|
@@ -174,9 +179,9 @@ export class Relay {
|
|
|
174
179
|
pingFrequency = 29_000;
|
|
175
180
|
/** How long to wait for EOSE response in milliseconds (default 20000) */
|
|
176
181
|
pingTimeout = 20_000;
|
|
177
|
-
/** Default
|
|
182
|
+
/** Default retry config for subscription() connection errors */
|
|
178
183
|
subscriptionReconnect;
|
|
179
|
-
/** Default
|
|
184
|
+
/** Default retry config for request() connection errors */
|
|
180
185
|
requestReconnect;
|
|
181
186
|
/** Default retry config for publish() method */
|
|
182
187
|
publishRetry;
|
|
@@ -224,8 +229,8 @@ export class Relay {
|
|
|
224
229
|
if (opts?.onUnresponsive !== undefined)
|
|
225
230
|
this.onUnresponsive = opts.onUnresponsive;
|
|
226
231
|
// Set retry configs
|
|
227
|
-
this.subscriptionReconnect =
|
|
228
|
-
this.requestReconnect =
|
|
232
|
+
this.subscriptionReconnect = normalizeRetryConfig(opts?.subscriptionReconnect);
|
|
233
|
+
this.requestReconnect = normalizeRetryConfig(opts?.requestReconnect);
|
|
229
234
|
this.publishRetry = { ...DEFAULT_RETRY_CONFIG, ...(opts?.publishRetry ?? {}) };
|
|
230
235
|
// Create an observable that tracks boolean authentication state
|
|
231
236
|
this.authenticated$ = this.authenticationResponse$.pipe(map((response) => response?.ok === true));
|
|
@@ -461,7 +466,12 @@ export class Relay {
|
|
|
461
466
|
send(message) {
|
|
462
467
|
this.socket.next(message);
|
|
463
468
|
}
|
|
464
|
-
/**
|
|
469
|
+
/**
|
|
470
|
+
* Create a REQ observable that emits OPEN, EVENT, EOSE, and CLOSED messages.
|
|
471
|
+
*
|
|
472
|
+
* `resubscribe` only repeats after the relay sends a clean CLOSED message for this REQ.
|
|
473
|
+
* `reconnect` only retries connection errors and does not retry relay CLOSED errors.
|
|
474
|
+
*/
|
|
465
475
|
req(filters, opts) {
|
|
466
476
|
const id = opts?.id ?? nanoid();
|
|
467
477
|
const waitForAuth = opts?.waitForAuth ?? true;
|
|
@@ -479,6 +489,7 @@ export class Relay {
|
|
|
479
489
|
const filtersComplete = input.pipe(ignoreElements(), endWith(true));
|
|
480
490
|
// Track whether the relay already sent CLOSED so we skip the redundant client CLOSE
|
|
481
491
|
let relayClosedSub = false;
|
|
492
|
+
let shouldResubscribe = false;
|
|
482
493
|
// Create an observable that filters responses from the relay to just the ones for this REQ
|
|
483
494
|
const messages = this.socket.pipe(filter((m) => Array.isArray(m) && (m[0] === "EVENT" || m[0] === "CLOSED" || m[0] === "EOSE") && m[1] === id),
|
|
484
495
|
// Map NIP-01 messages to RelayReqMessage
|
|
@@ -497,6 +508,7 @@ export class Relay {
|
|
|
497
508
|
const error = parseClosedError(m.reason);
|
|
498
509
|
if (error)
|
|
499
510
|
throw error;
|
|
511
|
+
shouldResubscribe = true;
|
|
500
512
|
}
|
|
501
513
|
}),
|
|
502
514
|
// Complete the stream on unprefixed CLOSED, emitting the CLOSED message last (inclusive)
|
|
@@ -509,6 +521,7 @@ export class Relay {
|
|
|
509
521
|
map((filters) => {
|
|
510
522
|
// Reset closed flag on each new REQ (resubscribe cycles)
|
|
511
523
|
relayClosedSub = false;
|
|
524
|
+
shouldResubscribe = false;
|
|
512
525
|
this.socket.next(["REQ", id, ...filters]);
|
|
513
526
|
// Add to tracking when REQ is sent
|
|
514
527
|
this.reqs$.next({ ...this.reqs$.value, [id]: filters });
|
|
@@ -541,7 +554,7 @@ export class Relay {
|
|
|
541
554
|
share());
|
|
542
555
|
// Wait for auth only when enabled and make sure to start the watch tower
|
|
543
556
|
const reqWithAuthStrategy = waitForAuth ? this.waitForAuth(this.authRequiredForRead$, observable) : observable;
|
|
544
|
-
return this.waitForReady(reqWithAuthStrategy).pipe(
|
|
557
|
+
return defer(() => this.waitForReady(reqWithAuthStrategy)).pipe(
|
|
545
558
|
// Retry only auth-required errors, optionally waiting for authentication first
|
|
546
559
|
retry({
|
|
547
560
|
delay: (error) => {
|
|
@@ -558,8 +571,10 @@ export class Relay {
|
|
|
558
571
|
return this.authenticated$.pipe(filter((authenticated) => authenticated), take(1));
|
|
559
572
|
},
|
|
560
573
|
}),
|
|
561
|
-
//
|
|
562
|
-
this.
|
|
574
|
+
// Retry connection errors independently from relay CLOSED errors
|
|
575
|
+
this.customConnectionRetryOperator(opts?.reconnect),
|
|
576
|
+
// Resubscribe only after the relay cleanly CLOSED this REQ
|
|
577
|
+
this.customRepeatOperator(opts?.resubscribe, () => shouldResubscribe),
|
|
563
578
|
// Only create one upstream subscription
|
|
564
579
|
share());
|
|
565
580
|
}
|
|
@@ -679,7 +694,7 @@ export class Relay {
|
|
|
679
694
|
const start = p instanceof Promise ? from(p) : of(p);
|
|
680
695
|
return lastValueFrom(start.pipe(switchMap((event) => this.auth(event))));
|
|
681
696
|
}
|
|
682
|
-
/** Internal operator for creating
|
|
697
|
+
/** Internal operator for creating a retry() operator */
|
|
683
698
|
customRetryOperator(times, base) {
|
|
684
699
|
if (times === false || times === undefined)
|
|
685
700
|
return identity;
|
|
@@ -690,16 +705,45 @@ export class Relay {
|
|
|
690
705
|
else
|
|
691
706
|
return retry({ ...base, ...times });
|
|
692
707
|
}
|
|
693
|
-
/** Internal operator for
|
|
694
|
-
|
|
708
|
+
/** Internal operator for retrying connection failures without retrying relay CLOSED errors */
|
|
709
|
+
customConnectionRetryOperator(times, base) {
|
|
695
710
|
if (times === false || times === undefined)
|
|
696
711
|
return identity;
|
|
697
|
-
|
|
698
|
-
|
|
712
|
+
const config = typeof times === "number" ? { ...base, count: times } : times === true ? (base ?? {}) : { ...base, ...times };
|
|
713
|
+
return retry({
|
|
714
|
+
...config,
|
|
715
|
+
delay: (error, count) => {
|
|
716
|
+
if (error instanceof RelayClosedError)
|
|
717
|
+
return throwError(() => error);
|
|
718
|
+
if (typeof config.delay === "number")
|
|
719
|
+
return timer(config.delay);
|
|
720
|
+
if (typeof config.delay === "function")
|
|
721
|
+
return config.delay(error, count);
|
|
722
|
+
return of(null);
|
|
723
|
+
},
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
/** Internal operator for creating the repeat() operator, optionally gated by a condition */
|
|
727
|
+
customRepeatOperator(times, condition) {
|
|
728
|
+
if (times === false || times === undefined)
|
|
729
|
+
return identity;
|
|
730
|
+
const delay = (repeatCount) => {
|
|
731
|
+
if (condition && !condition())
|
|
732
|
+
return EMPTY;
|
|
733
|
+
if (typeof times === "object") {
|
|
734
|
+
if (typeof times.delay === "number")
|
|
735
|
+
return timer(times.delay);
|
|
736
|
+
if (typeof times.delay === "function")
|
|
737
|
+
return times.delay(repeatCount);
|
|
738
|
+
}
|
|
739
|
+
return of(null);
|
|
740
|
+
};
|
|
741
|
+
if (times === true)
|
|
742
|
+
return repeat({ delay });
|
|
699
743
|
else if (typeof times === "number")
|
|
700
|
-
return repeat(times);
|
|
744
|
+
return repeat({ count: times, delay });
|
|
701
745
|
else
|
|
702
|
-
return repeat(times);
|
|
746
|
+
return repeat({ ...times, delay });
|
|
703
747
|
}
|
|
704
748
|
/** Internal operator for creating the timeout() operator */
|
|
705
749
|
customTimeoutOperator(timeout, defaultTimeout) {
|
|
@@ -713,7 +757,7 @@ export class Relay {
|
|
|
713
757
|
else
|
|
714
758
|
return simpleTimeout(timeout ?? defaultTimeout);
|
|
715
759
|
}
|
|
716
|
-
/** Creates a REQ that retries
|
|
760
|
+
/** Creates a persistent REQ that retries connection errors (default 3 retries) */
|
|
717
761
|
subscription(filters, opts) {
|
|
718
762
|
return this.req(filters, {
|
|
719
763
|
...opts,
|
|
@@ -726,7 +770,7 @@ export class Relay {
|
|
|
726
770
|
// Single subscription
|
|
727
771
|
share());
|
|
728
772
|
}
|
|
729
|
-
/** Makes a single request that
|
|
773
|
+
/** Makes a single request that retries connection errors and completes on EOSE */
|
|
730
774
|
request(filters, opts) {
|
|
731
775
|
const req = this.req(filters, {
|
|
732
776
|
...opts,
|
package/dist/types.d.ts
CHANGED
|
@@ -53,12 +53,12 @@ export type RelayReqOptions = {
|
|
|
53
53
|
*/
|
|
54
54
|
waitForAuth?: boolean;
|
|
55
55
|
/**
|
|
56
|
-
* Whether to resubscribe
|
|
56
|
+
* Whether to resubscribe after a clean CLOSED message from the relay. default is false
|
|
57
57
|
* @see https://rxjs.dev/api/index/function/repeat
|
|
58
58
|
*/
|
|
59
59
|
resubscribe?: boolean | number | Parameters<typeof repeat>[0];
|
|
60
60
|
/**
|
|
61
|
-
* Whether to
|
|
61
|
+
* Whether to retry connection errors. default is true (3 retries with linear backoff)
|
|
62
62
|
* @see https://rxjs.dev/api/index/function/retry
|
|
63
63
|
*/
|
|
64
64
|
reconnect?: boolean | number | Parameters<typeof retry>[0];
|