applesauce-relay 4.4.0 → 5.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.
- package/dist/group.d.ts +6 -7
- package/dist/group.js +6 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/negentropy.js +1 -1
- package/dist/management.d.ts +169 -0
- package/dist/management.js +202 -0
- package/dist/negentropy.d.ts +3 -3
- package/dist/operators/complete-on-eose.d.ts +1 -1
- package/dist/operators/only-events.d.ts +1 -1
- package/dist/operators/to-event-store.d.ts +1 -1
- package/dist/pool.d.ts +10 -8
- package/dist/pool.js +21 -8
- package/dist/relay.d.ts +25 -9
- package/dist/relay.js +75 -33
- package/dist/types.d.ts +28 -28
- package/package.json +10 -5
package/dist/group.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IAsyncEventStoreActions, IEventStoreActions } from "applesauce-core/event-store";
|
|
2
|
+
import type { Filter, NostrEvent } from "applesauce-core/helpers";
|
|
2
3
|
import { BehaviorSubject, MonoTypeOperatorFunction, Observable } from "rxjs";
|
|
3
|
-
import { IAsyncEventStoreActions, IAsyncEventStoreRead, IEventStoreActions, IEventStoreRead } from "applesauce-core";
|
|
4
|
-
import { type FilterWithAnd } from "applesauce-core/helpers";
|
|
5
4
|
import { NegentropySyncOptions, type ReconcileFunction } from "./negentropy.js";
|
|
6
5
|
import { SyncDirection } from "./relay.js";
|
|
7
|
-
import { CountResponse, FilterInput, IGroup, IGroupRelayInput, IRelay, PublishOptions, PublishResponse, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
|
|
6
|
+
import { CountResponse, FilterInput, IGroup, IGroupRelayInput, IRelay, NegentropyReadStore, NegentropySyncStore, PublishOptions, PublishResponse, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
|
|
8
7
|
/** Options for negentropy sync on a group of relays */
|
|
9
8
|
export type GroupNegentropySyncOptions = NegentropySyncOptions & {
|
|
10
9
|
/** Whether to sync in parallel (default true) */
|
|
@@ -47,7 +46,7 @@ export declare class RelayGroup implements IGroup {
|
|
|
47
46
|
/** Send an event to all relays */
|
|
48
47
|
event(event: NostrEvent): Observable<PublishResponse>;
|
|
49
48
|
/** Negentropy sync events with the relays and an event store */
|
|
50
|
-
negentropy(store:
|
|
49
|
+
negentropy(store: NegentropyReadStore, filter: Filter, reconcile: ReconcileFunction, opts?: GroupNegentropySyncOptions): Promise<boolean>;
|
|
51
50
|
/** Publish an event to all relays with retries ( default 3 retries ) */
|
|
52
51
|
publish(event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse[]>;
|
|
53
52
|
/** Request events from all relays and complete on EOSE */
|
|
@@ -55,7 +54,7 @@ export declare class RelayGroup implements IGroup {
|
|
|
55
54
|
/** Open a subscription to all relays with retries ( default 3 retries ) */
|
|
56
55
|
subscription(filters: FilterInput, opts?: GroupSubscriptionOptions): Observable<SubscriptionResponse>;
|
|
57
56
|
/** Count events on all relays in the group */
|
|
58
|
-
count(filters:
|
|
57
|
+
count(filters: Filter | Filter[], id?: string): Observable<Record<string, CountResponse>>;
|
|
59
58
|
/** Negentropy sync events with the relays and an event store */
|
|
60
|
-
sync(store:
|
|
59
|
+
sync(store: NegentropySyncStore | NostrEvent[], filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
|
|
61
60
|
}
|
package/dist/group.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { EventMemory } from "applesauce-core/event-store";
|
|
2
|
+
import { filterDuplicateEvents } from "applesauce-core/observable";
|
|
1
3
|
import { nanoid } from "nanoid";
|
|
2
4
|
import { BehaviorSubject, catchError, combineLatest, defaultIfEmpty, defer, endWith, filter, from, identity, ignoreElements, lastValueFrom, map, merge, of, scan, share, switchMap, take, takeWhile, toArray, } from "rxjs";
|
|
3
|
-
import { EventMemory, filterDuplicateEvents, } from "applesauce-core";
|
|
4
5
|
import { completeOnEose } from "./operators/complete-on-eose.js";
|
|
5
6
|
import { onlyEvents } from "./operators/only-events.js";
|
|
6
7
|
import { reverseSwitchMap } from "./operators/reverse-switch-map.js";
|
|
@@ -124,9 +125,7 @@ export class RelayGroup {
|
|
|
124
125
|
upstream.set(relay, observable);
|
|
125
126
|
}
|
|
126
127
|
return merge(...observables);
|
|
127
|
-
})
|
|
128
|
-
// Ensure a single upstream
|
|
129
|
-
share());
|
|
128
|
+
}));
|
|
130
129
|
}
|
|
131
130
|
/**
|
|
132
131
|
* Make a request to all relays
|
|
@@ -137,7 +136,9 @@ export class RelayGroup {
|
|
|
137
136
|
}
|
|
138
137
|
/** Send an event to all relays */
|
|
139
138
|
event(event) {
|
|
140
|
-
return this.internalPublish((relay) => relay.event(event))
|
|
139
|
+
return this.internalPublish((relay) => relay.event(event)).pipe(
|
|
140
|
+
// Ensure a single upstream subscription
|
|
141
|
+
share());
|
|
141
142
|
}
|
|
142
143
|
/** Negentropy sync events with the relays and an event store */
|
|
143
144
|
async negentropy(store, filter, reconcile, opts) {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/lib/negentropy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// (C) 2023 Doug Hoyte. MIT license
|
|
2
2
|
// Modified by hzrd149 to be TypeScript and work without the window.cyrpto.subtle API
|
|
3
|
-
import { sha256 } from "@noble/hashes/
|
|
3
|
+
import { sha256 } from "@noble/hashes/sha2";
|
|
4
4
|
const PROTOCOL_VERSION = 0x61; // Version 1
|
|
5
5
|
const ID_SIZE = 32;
|
|
6
6
|
const FINGERPRINT_SIZE = 16;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { logger } from "applesauce-core";
|
|
2
|
+
import { Observable } from "rxjs";
|
|
3
|
+
import { AuthSigner } from "./types.js";
|
|
4
|
+
import { Relay } from "./relay.js";
|
|
5
|
+
/** Base request structure for all NIP-86 requests */
|
|
6
|
+
export interface TRelayRequest<Method extends string, Params extends any[] = any[]> {
|
|
7
|
+
/** The method to call */
|
|
8
|
+
method: Method;
|
|
9
|
+
/** Parameters for the method */
|
|
10
|
+
params: Params;
|
|
11
|
+
}
|
|
12
|
+
/** Base response structure for all NIP-86 responses */
|
|
13
|
+
export type TRelayErrorResponse = {
|
|
14
|
+
/** Error message, non-null in case of error */
|
|
15
|
+
error: string;
|
|
16
|
+
result: null;
|
|
17
|
+
};
|
|
18
|
+
export type TRelaySuccessResponse<Result> = {
|
|
19
|
+
error: null;
|
|
20
|
+
/** Result object, null in case of error */
|
|
21
|
+
result: Result;
|
|
22
|
+
};
|
|
23
|
+
/** Merged relay management method and response types */
|
|
24
|
+
export type TRelayMethod<Method extends string = string, Params extends any[] = any[], Result extends any = any> = {
|
|
25
|
+
/** Method string */
|
|
26
|
+
method: Method;
|
|
27
|
+
/** Request type */
|
|
28
|
+
request: TRelayRequest<Method, Params>;
|
|
29
|
+
/** Response success type */
|
|
30
|
+
response: TRelaySuccessResponse<Result>;
|
|
31
|
+
/** Response error type */
|
|
32
|
+
error: TRelayErrorResponse;
|
|
33
|
+
};
|
|
34
|
+
export type SupportedMethodsMethod = TRelayMethod<"supportedmethods", [], string[]>;
|
|
35
|
+
export type BanPubkeyMethod = TRelayMethod<"banpubkey", [string, string?], true>;
|
|
36
|
+
export type ListBannedPubkeysMethod = TRelayMethod<"listbannedpubkeys", [], Array<{
|
|
37
|
+
pubkey: string;
|
|
38
|
+
reason?: string;
|
|
39
|
+
}>>;
|
|
40
|
+
export type AllowPubkeyMethod = TRelayMethod<"allowpubkey", [string, string?], true>;
|
|
41
|
+
export type ListAllowedPubkeysMethod = TRelayMethod<"listallowedpubkeys", [
|
|
42
|
+
], Array<{
|
|
43
|
+
pubkey: string;
|
|
44
|
+
reason?: string;
|
|
45
|
+
}>>;
|
|
46
|
+
export type ListEventsNeedingModerationMethod = TRelayMethod<"listeventsneedingmoderation", [
|
|
47
|
+
], Array<{
|
|
48
|
+
id: string;
|
|
49
|
+
reason?: string;
|
|
50
|
+
}>>;
|
|
51
|
+
export type AllowEventMethod = TRelayMethod<"allowevent", [string, string?], true>;
|
|
52
|
+
export type BanEventMethod = TRelayMethod<"banevent", [string, string?], true>;
|
|
53
|
+
export type ListBannedEventsMethod = TRelayMethod<"listbannedevents", [], Array<{
|
|
54
|
+
id: string;
|
|
55
|
+
reason?: string;
|
|
56
|
+
}>>;
|
|
57
|
+
export type ChangeRelayNameMethod = TRelayMethod<"changerelayname", [string], true>;
|
|
58
|
+
export type ChangeRelayDescriptionMethod = TRelayMethod<"changerelaydescription", [string], true>;
|
|
59
|
+
export type ChangeRelayIconMethod = TRelayMethod<"changerelayicon", [string], true>;
|
|
60
|
+
export type AllowKindMethod = TRelayMethod<"allowkind", [number], true>;
|
|
61
|
+
export type DisallowKindMethod = TRelayMethod<"disallowkind", [number], true>;
|
|
62
|
+
export type ListAllowedKindsMethod = TRelayMethod<"listallowedkinds", [], number[]>;
|
|
63
|
+
export type BlockIpMethod = TRelayMethod<"blockip", [string, string?], true>;
|
|
64
|
+
export type UnblockIpMethod = TRelayMethod<"unblockip", [string], true>;
|
|
65
|
+
export type ListBlockedIpsMethod = TRelayMethod<"listblockedips", [], Array<{
|
|
66
|
+
ip: string;
|
|
67
|
+
reason?: string;
|
|
68
|
+
}>>;
|
|
69
|
+
/** Union type for all relay management method definitions */
|
|
70
|
+
export type RelayManagementMethods = SupportedMethodsMethod | BanPubkeyMethod | ListBannedPubkeysMethod | AllowPubkeyMethod | ListAllowedPubkeysMethod | ListEventsNeedingModerationMethod | AllowEventMethod | BanEventMethod | ListBannedEventsMethod | ChangeRelayNameMethod | ChangeRelayDescriptionMethod | ChangeRelayIconMethod | AllowKindMethod | DisallowKindMethod | ListAllowedKindsMethod | BlockIpMethod | UnblockIpMethod | ListBlockedIpsMethod;
|
|
71
|
+
/** Custom error for relay management operations */
|
|
72
|
+
export declare class RelayManagementError extends Error {
|
|
73
|
+
readonly method?: string | undefined;
|
|
74
|
+
readonly statusCode?: number | undefined;
|
|
75
|
+
constructor(message: string, method?: string | undefined, statusCode?: number | undefined);
|
|
76
|
+
}
|
|
77
|
+
/** RelayManagement class for NIP-86 relay management API */
|
|
78
|
+
export declare class RelayManagement {
|
|
79
|
+
#private;
|
|
80
|
+
readonly relay: Relay;
|
|
81
|
+
readonly signer: AuthSigner;
|
|
82
|
+
protected log: typeof logger;
|
|
83
|
+
protected httpUrl: string;
|
|
84
|
+
constructor(relay: Relay, signer: AuthSigner);
|
|
85
|
+
/**
|
|
86
|
+
* Core request method that handles all RPC calls with NIP-98 authentication
|
|
87
|
+
*/
|
|
88
|
+
request<Method extends RelayManagementMethods>(method: Method["method"], params: Method["request"]["params"]): Promise<Method["response"]["result"]>;
|
|
89
|
+
/** Get list of supported methods */
|
|
90
|
+
supportedMethods(): Promise<string[]>;
|
|
91
|
+
/** Ban a pubkey */
|
|
92
|
+
banPubkey(pubkey: string, reason?: string): Promise<true>;
|
|
93
|
+
/** List all banned pubkeys */
|
|
94
|
+
listBannedPubkeys(): Promise<Array<{
|
|
95
|
+
pubkey: string;
|
|
96
|
+
reason?: string;
|
|
97
|
+
}>>;
|
|
98
|
+
/** Allow a pubkey */
|
|
99
|
+
allowPubkey(pubkey: string, reason?: string): Promise<true>;
|
|
100
|
+
/** List all allowed pubkeys */
|
|
101
|
+
listAllowedPubkeys(): Promise<Array<{
|
|
102
|
+
pubkey: string;
|
|
103
|
+
reason?: string;
|
|
104
|
+
}>>;
|
|
105
|
+
/** List events needing moderation */
|
|
106
|
+
listEventsNeedingModeration(): Promise<Array<{
|
|
107
|
+
id: string;
|
|
108
|
+
reason?: string;
|
|
109
|
+
}>>;
|
|
110
|
+
/** Allow an event */
|
|
111
|
+
allowEvent(eventId: string, reason?: string): Promise<true>;
|
|
112
|
+
/** Ban an event */
|
|
113
|
+
banEvent(eventId: string, reason?: string): Promise<true>;
|
|
114
|
+
/** List all banned events */
|
|
115
|
+
listBannedEvents(): Promise<Array<{
|
|
116
|
+
id: string;
|
|
117
|
+
reason?: string;
|
|
118
|
+
}>>;
|
|
119
|
+
/** Change relay name */
|
|
120
|
+
changeRelayName(name: string): Promise<true>;
|
|
121
|
+
/** Change relay description */
|
|
122
|
+
changeRelayDescription(description: string): Promise<true>;
|
|
123
|
+
/** Change relay icon */
|
|
124
|
+
changeRelayIcon(iconUrl: string): Promise<true>;
|
|
125
|
+
/** Allow a kind */
|
|
126
|
+
allowKind(kind: number): Promise<true>;
|
|
127
|
+
/** Disallow a kind */
|
|
128
|
+
disallowKind(kind: number): Promise<true>;
|
|
129
|
+
/** List all allowed kinds */
|
|
130
|
+
listAllowedKinds(): Promise<number[]>;
|
|
131
|
+
/** Block an IP address */
|
|
132
|
+
blockIp(ip: string, reason?: string): Promise<true>;
|
|
133
|
+
/** Unblock an IP address */
|
|
134
|
+
unblockIp(ip: string): Promise<true>;
|
|
135
|
+
/** List all blocked IPs */
|
|
136
|
+
listBlockedIps(): Promise<Array<{
|
|
137
|
+
ip: string;
|
|
138
|
+
reason?: string;
|
|
139
|
+
}>>;
|
|
140
|
+
/** Observable that emits supported methods when subscribed */
|
|
141
|
+
supportMethods$: Observable<string[]>;
|
|
142
|
+
/** Observable that emits banned pubkeys when subscribed */
|
|
143
|
+
bannedPubkeys$: Observable<Array<{
|
|
144
|
+
pubkey: string;
|
|
145
|
+
reason?: string;
|
|
146
|
+
}>>;
|
|
147
|
+
/** Observable that emits allowed pubkeys when subscribed */
|
|
148
|
+
allowedPubkeys$: Observable<Array<{
|
|
149
|
+
pubkey: string;
|
|
150
|
+
reason?: string;
|
|
151
|
+
}>>;
|
|
152
|
+
/** Observable that emits events needing moderation when subscribed */
|
|
153
|
+
eventsNeedingModeration$: Observable<Array<{
|
|
154
|
+
id: string;
|
|
155
|
+
reason?: string;
|
|
156
|
+
}>>;
|
|
157
|
+
/** Observable that emits banned events when subscribed */
|
|
158
|
+
bannedEvents$: Observable<Array<{
|
|
159
|
+
id: string;
|
|
160
|
+
reason?: string;
|
|
161
|
+
}>>;
|
|
162
|
+
/** Observable that emits allowed kinds when subscribed */
|
|
163
|
+
allowedKinds$: Observable<number[]>;
|
|
164
|
+
/** Observable that emits blocked IPs when subscribed */
|
|
165
|
+
blockedIps$: Observable<Array<{
|
|
166
|
+
ip: string;
|
|
167
|
+
reason?: string;
|
|
168
|
+
}>>;
|
|
169
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { logger } from "applesauce-core";
|
|
2
|
+
import { ensureHttpURL } from "applesauce-core/helpers/url";
|
|
3
|
+
import { getToken } from "nostr-tools/nip98";
|
|
4
|
+
import { BehaviorSubject, from, shareReplay, throwError } from "rxjs";
|
|
5
|
+
import { catchError, switchMap } from "rxjs/operators";
|
|
6
|
+
/** Custom error for relay management operations */
|
|
7
|
+
export class RelayManagementError extends Error {
|
|
8
|
+
method;
|
|
9
|
+
statusCode;
|
|
10
|
+
constructor(message, method, statusCode) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.method = method;
|
|
13
|
+
this.statusCode = statusCode;
|
|
14
|
+
this.name = "RelayManagementError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** RelayManagement class for NIP-86 relay management API */
|
|
18
|
+
export class RelayManagement {
|
|
19
|
+
relay;
|
|
20
|
+
signer;
|
|
21
|
+
log = logger.extend("RelayManagement");
|
|
22
|
+
httpUrl;
|
|
23
|
+
// Internal refresh triggers for observables
|
|
24
|
+
#refreshSupportMethods$ = new BehaviorSubject(undefined);
|
|
25
|
+
#refreshBannedPubkeys$ = new BehaviorSubject(undefined);
|
|
26
|
+
#refreshAllowedPubkeys$ = new BehaviorSubject(undefined);
|
|
27
|
+
#refreshEventsNeedingModeration$ = new BehaviorSubject(undefined);
|
|
28
|
+
#refreshBannedEvents$ = new BehaviorSubject(undefined);
|
|
29
|
+
#refreshAllowedKinds$ = new BehaviorSubject(undefined);
|
|
30
|
+
#refreshBlockedIps$ = new BehaviorSubject(undefined);
|
|
31
|
+
constructor(relay, signer) {
|
|
32
|
+
this.relay = relay;
|
|
33
|
+
this.signer = signer;
|
|
34
|
+
this.log = this.log.extend(relay.url);
|
|
35
|
+
this.httpUrl = ensureHttpURL(relay.url);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Core request method that handles all RPC calls with NIP-98 authentication
|
|
39
|
+
*/
|
|
40
|
+
async request(method, params) {
|
|
41
|
+
const requestBody = {
|
|
42
|
+
method,
|
|
43
|
+
params,
|
|
44
|
+
};
|
|
45
|
+
const requestBodyString = JSON.stringify(requestBody);
|
|
46
|
+
// Generate NIP-98 token using getToken from nostr-tools
|
|
47
|
+
const authHeader = await getToken(this.httpUrl, "POST", (event) => this.signer.signEvent(event), true, // includeAuthorizationScheme - returns "Nostr <token>" format
|
|
48
|
+
requestBody);
|
|
49
|
+
// Make the HTTP request
|
|
50
|
+
const response = await fetch(this.httpUrl, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/nostr+json+rpc",
|
|
54
|
+
Authorization: authHeader,
|
|
55
|
+
},
|
|
56
|
+
body: requestBodyString,
|
|
57
|
+
});
|
|
58
|
+
// Handle HTTP errors
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
if (response.status === 401) {
|
|
61
|
+
throw new RelayManagementError("Unauthorized: Invalid or missing NIP-98 authentication", method, response.status);
|
|
62
|
+
}
|
|
63
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
64
|
+
throw new RelayManagementError(`HTTP ${response.status}: ${errorText}`, method, response.status);
|
|
65
|
+
}
|
|
66
|
+
// Parse the response
|
|
67
|
+
const data = await response.json();
|
|
68
|
+
// Handle RPC errors
|
|
69
|
+
if (data.error) {
|
|
70
|
+
throw new RelayManagementError(`RPC error: ${data.error}`, method, response.status);
|
|
71
|
+
}
|
|
72
|
+
return data.result;
|
|
73
|
+
}
|
|
74
|
+
// Convenience methods for each RPC call
|
|
75
|
+
/** Get list of supported methods */
|
|
76
|
+
async supportedMethods() {
|
|
77
|
+
return this.request("supportedmethods", []);
|
|
78
|
+
}
|
|
79
|
+
/** Ban a pubkey */
|
|
80
|
+
async banPubkey(pubkey, reason) {
|
|
81
|
+
const result = await this.request("banpubkey", reason ? [pubkey, reason] : [pubkey]);
|
|
82
|
+
this.#refreshBannedPubkeys$.next();
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
/** List all banned pubkeys */
|
|
86
|
+
async listBannedPubkeys() {
|
|
87
|
+
return this.request("listbannedpubkeys", []);
|
|
88
|
+
}
|
|
89
|
+
/** Allow a pubkey */
|
|
90
|
+
async allowPubkey(pubkey, reason) {
|
|
91
|
+
const result = await this.request("allowpubkey", reason ? [pubkey, reason] : [pubkey]);
|
|
92
|
+
this.#refreshAllowedPubkeys$.next();
|
|
93
|
+
this.#refreshBannedPubkeys$.next(); // Also refresh banned list in case it was unbanned
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
/** List all allowed pubkeys */
|
|
97
|
+
async listAllowedPubkeys() {
|
|
98
|
+
return this.request("listallowedpubkeys", []);
|
|
99
|
+
}
|
|
100
|
+
/** List events needing moderation */
|
|
101
|
+
async listEventsNeedingModeration() {
|
|
102
|
+
return this.request("listeventsneedingmoderation", []);
|
|
103
|
+
}
|
|
104
|
+
/** Allow an event */
|
|
105
|
+
async allowEvent(eventId, reason) {
|
|
106
|
+
const result = await this.request("allowevent", reason ? [eventId, reason] : [eventId]);
|
|
107
|
+
this.#refreshBannedEvents$.next(); // Also refresh banned list in case it was unbanned
|
|
108
|
+
this.#refreshEventsNeedingModeration$.next();
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
/** Ban an event */
|
|
112
|
+
async banEvent(eventId, reason) {
|
|
113
|
+
const result = await this.request("banevent", reason ? [eventId, reason] : [eventId]);
|
|
114
|
+
this.#refreshBannedEvents$.next();
|
|
115
|
+
this.#refreshEventsNeedingModeration$.next();
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
/** List all banned events */
|
|
119
|
+
async listBannedEvents() {
|
|
120
|
+
return this.request("listbannedevents", []);
|
|
121
|
+
}
|
|
122
|
+
/** Change relay name */
|
|
123
|
+
async changeRelayName(name) {
|
|
124
|
+
return this.request("changerelayname", [name]);
|
|
125
|
+
}
|
|
126
|
+
/** Change relay description */
|
|
127
|
+
async changeRelayDescription(description) {
|
|
128
|
+
return this.request("changerelaydescription", [description]);
|
|
129
|
+
}
|
|
130
|
+
/** Change relay icon */
|
|
131
|
+
async changeRelayIcon(iconUrl) {
|
|
132
|
+
return this.request("changerelayicon", [iconUrl]);
|
|
133
|
+
}
|
|
134
|
+
/** Allow a kind */
|
|
135
|
+
async allowKind(kind) {
|
|
136
|
+
const result = await this.request("allowkind", [kind]);
|
|
137
|
+
this.#refreshAllowedKinds$.next();
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
/** Disallow a kind */
|
|
141
|
+
async disallowKind(kind) {
|
|
142
|
+
const result = await this.request("disallowkind", [kind]);
|
|
143
|
+
this.#refreshAllowedKinds$.next();
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
/** List all allowed kinds */
|
|
147
|
+
async listAllowedKinds() {
|
|
148
|
+
return this.request("listallowedkinds", []);
|
|
149
|
+
}
|
|
150
|
+
/** Block an IP address */
|
|
151
|
+
async blockIp(ip, reason) {
|
|
152
|
+
const result = await this.request("blockip", reason ? [ip, reason] : [ip]);
|
|
153
|
+
this.#refreshBlockedIps$.next();
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
/** Unblock an IP address */
|
|
157
|
+
async unblockIp(ip) {
|
|
158
|
+
const result = await this.request("unblockip", [ip]);
|
|
159
|
+
this.#refreshBlockedIps$.next();
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
/** List all blocked IPs */
|
|
163
|
+
async listBlockedIps() {
|
|
164
|
+
return this.request("listblockedips", []);
|
|
165
|
+
}
|
|
166
|
+
// Reactive observables for list methods
|
|
167
|
+
/** Observable that emits supported methods when subscribed */
|
|
168
|
+
supportMethods$ = this.#refreshSupportMethods$.pipe(switchMap(() => from(this.supportedMethods())), catchError((error) => {
|
|
169
|
+
this.log("Error fetching supported methods:", error);
|
|
170
|
+
return throwError(() => error);
|
|
171
|
+
}), shareReplay(1));
|
|
172
|
+
/** Observable that emits banned pubkeys when subscribed */
|
|
173
|
+
bannedPubkeys$ = this.#refreshBannedPubkeys$.pipe(switchMap(() => from(this.listBannedPubkeys())), catchError((error) => {
|
|
174
|
+
this.log("Error fetching banned pubkeys:", error);
|
|
175
|
+
return throwError(() => error);
|
|
176
|
+
}), shareReplay(1));
|
|
177
|
+
/** Observable that emits allowed pubkeys when subscribed */
|
|
178
|
+
allowedPubkeys$ = this.#refreshAllowedPubkeys$.pipe(switchMap(() => from(this.listAllowedPubkeys())), catchError((error) => {
|
|
179
|
+
this.log("Error fetching allowed pubkeys:", error);
|
|
180
|
+
return throwError(() => error);
|
|
181
|
+
}), shareReplay(1));
|
|
182
|
+
/** Observable that emits events needing moderation when subscribed */
|
|
183
|
+
eventsNeedingModeration$ = this.#refreshEventsNeedingModeration$.pipe(switchMap(() => from(this.listEventsNeedingModeration())), catchError((error) => {
|
|
184
|
+
this.log("Error fetching events needing moderation:", error);
|
|
185
|
+
return throwError(() => error);
|
|
186
|
+
}), shareReplay(1));
|
|
187
|
+
/** Observable that emits banned events when subscribed */
|
|
188
|
+
bannedEvents$ = this.#refreshBannedEvents$.pipe(switchMap(() => from(this.listBannedEvents())), catchError((error) => {
|
|
189
|
+
this.log("Error fetching banned events:", error);
|
|
190
|
+
return throwError(() => error);
|
|
191
|
+
}), shareReplay(1));
|
|
192
|
+
/** Observable that emits allowed kinds when subscribed */
|
|
193
|
+
allowedKinds$ = this.#refreshAllowedKinds$.pipe(switchMap(() => from(this.listAllowedKinds())), catchError((error) => {
|
|
194
|
+
this.log("Error fetching allowed kinds:", error);
|
|
195
|
+
return throwError(() => error);
|
|
196
|
+
}), shareReplay(1));
|
|
197
|
+
/** Observable that emits blocked IPs when subscribed */
|
|
198
|
+
blockedIps$ = this.#refreshBlockedIps$.pipe(switchMap(() => from(this.listBlockedIps())), catchError((error) => {
|
|
199
|
+
this.log("Error fetching blocked IPs:", error);
|
|
200
|
+
return throwError(() => error);
|
|
201
|
+
}), shareReplay(1));
|
|
202
|
+
}
|
package/dist/negentropy.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IAsyncEventStoreRead, IEventStoreRead } from "applesauce-core";
|
|
2
|
-
import { type
|
|
2
|
+
import { type Filter } from "applesauce-core/helpers";
|
|
3
3
|
import { NegentropyStorageVector } from "./lib/negentropy.js";
|
|
4
4
|
import { MultiplexWebSocket } from "./types.js";
|
|
5
5
|
/**
|
|
@@ -15,7 +15,7 @@ export type NegentropySyncOptions = {
|
|
|
15
15
|
signal?: AbortSignal;
|
|
16
16
|
};
|
|
17
17
|
/** Creates a NegentropyStorageVector from an event store and filter */
|
|
18
|
-
export declare function buildStorageFromFilter(store: IEventStoreRead | IAsyncEventStoreRead, filter:
|
|
18
|
+
export declare function buildStorageFromFilter(store: IEventStoreRead | IAsyncEventStoreRead, filter: Filter): Promise<NegentropyStorageVector>;
|
|
19
19
|
/** Creates a NegentropyStorageVector from an array of items */
|
|
20
20
|
export declare function buildStorageVector(items: {
|
|
21
21
|
id: string;
|
|
@@ -28,4 +28,4 @@ export declare function buildStorageVector(items: {
|
|
|
28
28
|
*/
|
|
29
29
|
export declare function negentropySync(storage: NegentropyStorageVector, socket: MultiplexWebSocket & {
|
|
30
30
|
next: (msg: any) => void;
|
|
31
|
-
}, filter:
|
|
31
|
+
}, filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MonoTypeOperatorFunction, OperatorFunction } from "rxjs";
|
|
2
|
-
import { NostrEvent } from "
|
|
2
|
+
import { NostrEvent } from "applesauce-core/helpers/event";
|
|
3
3
|
import { SubscriptionResponse } from "../types.js";
|
|
4
4
|
export declare function completeOnEose(includeEose: true): MonoTypeOperatorFunction<SubscriptionResponse>;
|
|
5
5
|
export declare function completeOnEose(): OperatorFunction<SubscriptionResponse, NostrEvent>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OperatorFunction } from "rxjs";
|
|
2
|
-
import { NostrEvent } from "
|
|
2
|
+
import { NostrEvent } from "applesauce-core/helpers/event";
|
|
3
3
|
import { SubscriptionResponse } from "../types.js";
|
|
4
4
|
/** Filter subscription responses and only return the events */
|
|
5
5
|
export declare function onlyEvents(): OperatorFunction<SubscriptionResponse, NostrEvent>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OperatorFunction } from "rxjs";
|
|
2
2
|
import { IEventStore } from "applesauce-core";
|
|
3
|
-
import { NostrEvent } from "
|
|
3
|
+
import { NostrEvent } from "applesauce-core/helpers/event";
|
|
4
4
|
import { SubscriptionResponse } from "../types.js";
|
|
5
5
|
/**
|
|
6
6
|
* Adds all events to event store and returns a deduplicated timeline when EOSE is received
|
package/dist/pool.d.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import type { NostrEvent } from "applesauce-core/helpers/event";
|
|
2
|
+
import { Filter } from "applesauce-core/helpers/filter";
|
|
3
|
+
import { FilterMap, OutboxMap } from "applesauce-core/helpers/relay-selection";
|
|
4
4
|
import { BehaviorSubject, Observable, Subject } from "rxjs";
|
|
5
5
|
import { RelayGroup } from "./group.js";
|
|
6
6
|
import type { NegentropySyncOptions, ReconcileFunction } from "./negentropy.js";
|
|
7
7
|
import { Relay, SyncDirection, type RelayOptions } from "./relay.js";
|
|
8
|
-
import type { CountResponse, FilterInput, IPool, IPoolRelayInput, IRelay, PublishResponse, SubscriptionResponse } from "./types.js";
|
|
8
|
+
import type { CountResponse, FilterInput, IPool, IPoolRelayInput, IRelay, NegentropyReadStore, NegentropySyncStore, PublishResponse, SubscriptionResponse } from "./types.js";
|
|
9
9
|
export declare class RelayPool implements IPool {
|
|
10
10
|
options?: RelayOptions | undefined;
|
|
11
11
|
relays$: BehaviorSubject<Map<string, Relay>>;
|
|
12
12
|
get relays(): Map<string, Relay>;
|
|
13
|
+
/** Whether to ignore relays that are ready=false */
|
|
14
|
+
ignoreOffline: boolean;
|
|
13
15
|
/** A signal when a relay is added */
|
|
14
16
|
add$: Subject<IRelay>;
|
|
15
17
|
/** A signal when a relay is removed */
|
|
@@ -18,7 +20,7 @@ export declare class RelayPool implements IPool {
|
|
|
18
20
|
/** Get or create a new relay connection */
|
|
19
21
|
relay(url: string): Relay;
|
|
20
22
|
/** Create a group of relays */
|
|
21
|
-
group(relays: IPoolRelayInput): RelayGroup;
|
|
23
|
+
group(relays: IPoolRelayInput, ignoreOffline?: boolean): RelayGroup;
|
|
22
24
|
/** Removes a relay from the pool and defaults to closing the connection */
|
|
23
25
|
remove(relay: string | IRelay, close?: boolean): void;
|
|
24
26
|
/** Make a REQ to multiple relays that does not deduplicate events */
|
|
@@ -26,7 +28,7 @@ export declare class RelayPool implements IPool {
|
|
|
26
28
|
/** Send an EVENT message to multiple relays */
|
|
27
29
|
event(relays: IPoolRelayInput, event: NostrEvent): Observable<PublishResponse>;
|
|
28
30
|
/** Negentropy sync event ids with the relays and an event store */
|
|
29
|
-
negentropy(relays: IPoolRelayInput, store:
|
|
31
|
+
negentropy(relays: IPoolRelayInput, store: NegentropyReadStore, filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
|
|
30
32
|
/** Publish an event to multiple relays */
|
|
31
33
|
publish(relays: IPoolRelayInput, event: Parameters<RelayGroup["publish"]>[0], opts?: Parameters<RelayGroup["publish"]>[1]): Promise<PublishResponse[]>;
|
|
32
34
|
/** Request events from multiple relays */
|
|
@@ -38,7 +40,7 @@ export declare class RelayPool implements IPool {
|
|
|
38
40
|
/** Open a subscription for an {@link OutboxMap} and filter */
|
|
39
41
|
outboxSubscription(outboxes: OutboxMap | Observable<OutboxMap>, filter: Omit<Filter, "authors">, options?: Parameters<RelayGroup["subscription"]>[1]): Observable<SubscriptionResponse>;
|
|
40
42
|
/** Count events on multiple relays */
|
|
41
|
-
count(relays: IPoolRelayInput, filters:
|
|
43
|
+
count(relays: IPoolRelayInput, filters: Filter | Filter[], id?: string): Observable<Record<string, CountResponse>>;
|
|
42
44
|
/** Negentropy sync events with the relays and an event store */
|
|
43
|
-
sync(relays: IPoolRelayInput, store:
|
|
45
|
+
sync(relays: IPoolRelayInput, store: NegentropySyncStore | NostrEvent[], filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
|
|
44
46
|
}
|
package/dist/pool.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isFilterEqual } from "applesauce-core/helpers/filter";
|
|
2
|
+
import { createFilterMap } from "applesauce-core/helpers/relay-selection";
|
|
3
|
+
import { normalizeURL } from "applesauce-core/helpers/url";
|
|
2
4
|
import { BehaviorSubject, distinctUntilChanged, isObservable, map, of, Subject } from "rxjs";
|
|
3
5
|
import { RelayGroup } from "./group.js";
|
|
4
6
|
import { Relay } from "./relay.js";
|
|
@@ -8,6 +10,8 @@ export class RelayPool {
|
|
|
8
10
|
get relays() {
|
|
9
11
|
return this.relays$.value;
|
|
10
12
|
}
|
|
13
|
+
/** Whether to ignore relays that are ready=false */
|
|
14
|
+
ignoreOffline = true;
|
|
11
15
|
/** A signal when a relay is added */
|
|
12
16
|
add$ = new Subject();
|
|
13
17
|
/** A signal when a relay is removed */
|
|
@@ -27,14 +31,21 @@ export class RelayPool {
|
|
|
27
31
|
relay = new Relay(url, this.options);
|
|
28
32
|
this.relays.set(url, relay);
|
|
29
33
|
this.relays$.next(this.relays);
|
|
30
|
-
this.add$.next(relay);
|
|
31
34
|
return relay;
|
|
32
35
|
}
|
|
33
36
|
/** Create a group of relays */
|
|
34
|
-
group(relays) {
|
|
35
|
-
|
|
37
|
+
group(relays, ignoreOffline = this.ignoreOffline) {
|
|
38
|
+
let input = Array.isArray(relays)
|
|
36
39
|
? relays.map((url) => this.relay(url))
|
|
37
|
-
: relays.pipe(map((urls) => urls.map((url) => this.relay(url))))
|
|
40
|
+
: relays.pipe(map((urls) => urls.map((url) => this.relay(url))));
|
|
41
|
+
if (ignoreOffline) {
|
|
42
|
+
// Filter out the offline relays
|
|
43
|
+
if (Array.isArray(input))
|
|
44
|
+
input = input.filter((relay) => relay.ready);
|
|
45
|
+
else
|
|
46
|
+
input = input.pipe(map((relays) => relays.filter((relay) => relay.ready)));
|
|
47
|
+
}
|
|
48
|
+
return new RelayGroup(input);
|
|
38
49
|
}
|
|
39
50
|
/** Removes a relay from the pool and defaults to closing the connection */
|
|
40
51
|
remove(relay, close = true) {
|
|
@@ -57,15 +68,17 @@ export class RelayPool {
|
|
|
57
68
|
}
|
|
58
69
|
/** Make a REQ to multiple relays that does not deduplicate events */
|
|
59
70
|
req(relays, filters, id) {
|
|
60
|
-
|
|
71
|
+
// Never filter out offline relays in manual methods
|
|
72
|
+
return this.group(relays, false).req(filters, id);
|
|
61
73
|
}
|
|
62
74
|
/** Send an EVENT message to multiple relays */
|
|
63
75
|
event(relays, event) {
|
|
64
|
-
|
|
76
|
+
// Never filter out offline relays in manual methods
|
|
77
|
+
return this.group(relays, false).event(event);
|
|
65
78
|
}
|
|
66
79
|
/** Negentropy sync event ids with the relays and an event store */
|
|
67
80
|
negentropy(relays, store, filter, reconcile, opts) {
|
|
68
|
-
return this.group(relays).negentropy(store, filter, reconcile, opts);
|
|
81
|
+
return this.group(relays, false).negentropy(store, filter, reconcile, opts);
|
|
69
82
|
}
|
|
70
83
|
/** Publish an event to multiple relays */
|
|
71
84
|
publish(relays, event, opts) {
|
package/dist/relay.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { RelayInformation } from "nostr-tools/nip11";
|
|
1
|
+
import { logger } from "applesauce-core";
|
|
2
|
+
import { NostrEvent } from "applesauce-core/helpers/event";
|
|
3
|
+
import { Filter } from "applesauce-core/helpers/filter";
|
|
5
4
|
import { BehaviorSubject, MonoTypeOperatorFunction, Observable, RepeatConfig, RetryConfig, Subject } from "rxjs";
|
|
6
5
|
import { WebSocketSubject, WebSocketSubjectConfig } from "rxjs/webSocket";
|
|
7
6
|
import { type NegentropySyncOptions, type ReconcileFunction } from "./negentropy.js";
|
|
8
|
-
import { AuthSigner, CountResponse, FilterInput, IRelay, PublishOptions, PublishResponse, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
|
|
7
|
+
import { AuthSigner, CountResponse, FilterInput, IRelay, NegentropyReadStore, NegentropySyncStore, PublishOptions, PublishResponse, RelayInformation, RequestOptions, SubscriptionOptions, SubscriptionResponse } from "./types.js";
|
|
9
8
|
/** Flags for the negentropy sync type */
|
|
10
9
|
export declare enum SyncDirection {
|
|
11
10
|
RECEIVE = 1,
|
|
@@ -26,13 +25,21 @@ export type RelayOptions = {
|
|
|
26
25
|
publishTimeout?: number;
|
|
27
26
|
/** How long to keep the connection alive after nothing is subscribed (default 30s) */
|
|
28
27
|
keepAlive?: number;
|
|
28
|
+
/** Default retry config for subscription() method */
|
|
29
|
+
subscriptionRetry?: RetryConfig;
|
|
30
|
+
/** Default retry config for request() method */
|
|
31
|
+
requestRetry?: RetryConfig;
|
|
32
|
+
/** Default retry config for publish() method */
|
|
33
|
+
publishRetry?: RetryConfig;
|
|
29
34
|
};
|
|
30
35
|
export declare class Relay implements IRelay {
|
|
31
36
|
url: string;
|
|
32
37
|
protected log: typeof logger;
|
|
33
38
|
protected socket: WebSocketSubject<any>;
|
|
39
|
+
/** Internal subject that tracks the ready state of the relay */
|
|
40
|
+
protected _ready$: BehaviorSubject<boolean>;
|
|
34
41
|
/** Whether the relay is ready for subscriptions or event publishing. setting this to false will cause all .req and .event observables to hang until the relay is ready */
|
|
35
|
-
|
|
42
|
+
ready$: Observable<boolean>;
|
|
36
43
|
/** A method that returns an Observable that emits when the relay should reconnect */
|
|
37
44
|
reconnectTimer: (error: CloseEvent | Error, attempts: number) => Observable<number>;
|
|
38
45
|
/** How many times the relay has tried to reconnect */
|
|
@@ -62,6 +69,8 @@ export declare class Relay implements IRelay {
|
|
|
62
69
|
/** An observable that emits the NIP-11 information document for the relay */
|
|
63
70
|
information$: Observable<RelayInformation | null>;
|
|
64
71
|
protected _nip11: RelayInformation | null;
|
|
72
|
+
/** An observable that emits the icon URL for the relay, or the favicon.ico URL for the relay */
|
|
73
|
+
icon$: Observable<string | undefined>;
|
|
65
74
|
/** An observable that emits the limitations for the relay */
|
|
66
75
|
limitations$: Observable<RelayInformation["limitation"] | null>;
|
|
67
76
|
/** An array of supported NIPs from the NIP-11 information document */
|
|
@@ -72,6 +81,7 @@ export declare class Relay implements IRelay {
|
|
|
72
81
|
close$: Subject<CloseEvent>;
|
|
73
82
|
/** An observable that emits when underlying websocket is closing due to unsubscription */
|
|
74
83
|
closing$: Subject<void>;
|
|
84
|
+
get ready(): boolean;
|
|
75
85
|
get connected(): boolean;
|
|
76
86
|
get challenge(): string | null;
|
|
77
87
|
get notices(): string[];
|
|
@@ -86,6 +96,12 @@ export declare class Relay implements IRelay {
|
|
|
86
96
|
publishTimeout: number;
|
|
87
97
|
/** How long to keep the connection alive after nothing is subscribed (default 30s) */
|
|
88
98
|
keepAlive: number;
|
|
99
|
+
/** Default retry config for subscription() method */
|
|
100
|
+
protected subscriptionReconnect: RetryConfig;
|
|
101
|
+
/** Default retry config for request() method */
|
|
102
|
+
protected requestReconnect: RetryConfig;
|
|
103
|
+
/** Default retry config for publish() method */
|
|
104
|
+
protected publishRetry: RetryConfig;
|
|
89
105
|
protected receivedAuthRequiredForReq: BehaviorSubject<boolean>;
|
|
90
106
|
protected receivedAuthRequiredForEvent: BehaviorSubject<boolean>;
|
|
91
107
|
authRequiredForRead$: Observable<boolean>;
|
|
@@ -106,13 +122,13 @@ export declare class Relay implements IRelay {
|
|
|
106
122
|
/** Create a REQ observable that emits events or "EOSE" or errors */
|
|
107
123
|
req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
|
|
108
124
|
/** Create a COUNT observable that emits a single count response */
|
|
109
|
-
count(filters:
|
|
125
|
+
count(filters: Filter | Filter[], id?: string): Observable<CountResponse>;
|
|
110
126
|
/** Send an EVENT or AUTH message and return an observable of PublishResponse that completes or errors */
|
|
111
127
|
event(event: NostrEvent, verb?: "EVENT" | "AUTH"): Observable<PublishResponse>;
|
|
112
128
|
/** send and AUTH message */
|
|
113
129
|
auth(event: NostrEvent): Promise<PublishResponse>;
|
|
114
130
|
/** Negentropy sync event ids with the relay and an event store */
|
|
115
|
-
negentropy(store:
|
|
131
|
+
negentropy(store: NegentropyReadStore, filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
|
|
116
132
|
/** Authenticate with the relay using a signer */
|
|
117
133
|
authenticate(signer: AuthSigner): Promise<PublishResponse>;
|
|
118
134
|
/** Internal operator for creating the retry() operator */
|
|
@@ -130,7 +146,7 @@ export declare class Relay implements IRelay {
|
|
|
130
146
|
/** Publishes an event to the relay and retries when relay errors or responds with auth-required ( default 3 retries ) */
|
|
131
147
|
publish(event: NostrEvent, opts?: PublishOptions): Promise<PublishResponse>;
|
|
132
148
|
/** Negentropy sync events with the relay and an event store */
|
|
133
|
-
sync(store:
|
|
149
|
+
sync(store: NegentropySyncStore, filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
|
|
134
150
|
/** Force close the connection */
|
|
135
151
|
close(): void;
|
|
136
152
|
/** An async method that returns the NIP-11 information document for the relay */
|
package/dist/relay.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { logger } from "applesauce-core";
|
|
2
|
-
import { ensureHttpURL } from "applesauce-core/helpers";
|
|
3
|
-
import { simpleTimeout } from "applesauce-core/observable";
|
|
2
|
+
import { ensureHttpURL } from "applesauce-core/helpers/url";
|
|
3
|
+
import { mapEventsToStore, simpleTimeout } from "applesauce-core/observable";
|
|
4
4
|
import { nanoid } from "nanoid";
|
|
5
|
-
import {
|
|
5
|
+
import { makeAuthEvent } from "nostr-tools/nip42";
|
|
6
6
|
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, Subject, switchMap, take, takeUntil, tap, throwError, timeout, timer, } from "rxjs";
|
|
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
10
|
const AUTH_REQUIRED_PREFIX = "auth-required:";
|
|
11
|
-
|
|
11
|
+
/** Default retry config for all methods */
|
|
12
|
+
const DEFAULT_RETRY_CONFIG = {
|
|
13
|
+
count: 3,
|
|
14
|
+
delay: 1000,
|
|
15
|
+
resetOnSuccess: true,
|
|
16
|
+
};
|
|
12
17
|
/** Flags for the negentropy sync type */
|
|
13
18
|
export var SyncDirection;
|
|
14
19
|
(function (SyncDirection) {
|
|
@@ -23,8 +28,10 @@ export class Relay {
|
|
|
23
28
|
url;
|
|
24
29
|
log = logger.extend("Relay");
|
|
25
30
|
socket;
|
|
31
|
+
/** Internal subject that tracks the ready state of the relay */
|
|
32
|
+
_ready$ = new BehaviorSubject(true);
|
|
26
33
|
/** Whether the relay is ready for subscriptions or event publishing. setting this to false will cause all .req and .event observables to hang until the relay is ready */
|
|
27
|
-
ready$ =
|
|
34
|
+
ready$ = this._ready$.asObservable();
|
|
28
35
|
/** A method that returns an Observable that emits when the relay should reconnect */
|
|
29
36
|
reconnectTimer;
|
|
30
37
|
/** How many times the relay has tried to reconnect */
|
|
@@ -54,6 +61,8 @@ export class Relay {
|
|
|
54
61
|
/** An observable that emits the NIP-11 information document for the relay */
|
|
55
62
|
information$;
|
|
56
63
|
_nip11 = null;
|
|
64
|
+
/** An observable that emits the icon URL for the relay, or the favicon.ico URL for the relay */
|
|
65
|
+
icon$;
|
|
57
66
|
/** An observable that emits the limitations for the relay */
|
|
58
67
|
limitations$;
|
|
59
68
|
/** An array of supported NIPs from the NIP-11 information document */
|
|
@@ -65,6 +74,9 @@ export class Relay {
|
|
|
65
74
|
/** An observable that emits when underlying websocket is closing due to unsubscription */
|
|
66
75
|
closing$ = new Subject();
|
|
67
76
|
// sync state
|
|
77
|
+
get ready() {
|
|
78
|
+
return this._ready$.value;
|
|
79
|
+
}
|
|
68
80
|
get connected() {
|
|
69
81
|
return this.connected$.value;
|
|
70
82
|
}
|
|
@@ -91,6 +103,12 @@ export class Relay {
|
|
|
91
103
|
publishTimeout = 30_000;
|
|
92
104
|
/** How long to keep the connection alive after nothing is subscribed (default 30s) */
|
|
93
105
|
keepAlive = 30_000;
|
|
106
|
+
/** Default retry config for subscription() method */
|
|
107
|
+
subscriptionReconnect;
|
|
108
|
+
/** Default retry config for request() method */
|
|
109
|
+
requestReconnect;
|
|
110
|
+
/** Default retry config for publish() method */
|
|
111
|
+
publishRetry;
|
|
94
112
|
// Subjects that track if an "auth-required" message has been received for REQ or EVENT
|
|
95
113
|
receivedAuthRequiredForReq = new BehaviorSubject(false);
|
|
96
114
|
receivedAuthRequiredForEvent = new BehaviorSubject(false);
|
|
@@ -124,6 +142,10 @@ export class Relay {
|
|
|
124
142
|
this.publishTimeout = opts.publishTimeout;
|
|
125
143
|
if (opts?.keepAlive !== undefined)
|
|
126
144
|
this.keepAlive = opts.keepAlive;
|
|
145
|
+
// Set retry configs
|
|
146
|
+
this.subscriptionReconnect = { ...DEFAULT_RETRY_CONFIG, ...(opts?.subscriptionRetry ?? {}) };
|
|
147
|
+
this.requestReconnect = { ...DEFAULT_RETRY_CONFIG, ...(opts?.requestRetry ?? {}) };
|
|
148
|
+
this.publishRetry = { ...DEFAULT_RETRY_CONFIG, ...(opts?.publishRetry ?? {}) };
|
|
127
149
|
// Create an observable that tracks boolean authentication state
|
|
128
150
|
this.authenticated$ = this.authenticationResponse$.pipe(map((response) => response?.ok === true));
|
|
129
151
|
/** Use the static method to create a new reconnect method for this relay */
|
|
@@ -134,12 +156,20 @@ export class Relay {
|
|
|
134
156
|
this.connected$.next(true);
|
|
135
157
|
this.attempts$.next(0);
|
|
136
158
|
this.error$.next(null);
|
|
159
|
+
// Reset to clean state
|
|
137
160
|
this.resetState();
|
|
138
161
|
});
|
|
139
162
|
this.close$.subscribe((event) => {
|
|
140
|
-
this.
|
|
141
|
-
|
|
163
|
+
if (this.connected$.value)
|
|
164
|
+
this.log("Disconnected");
|
|
165
|
+
else
|
|
166
|
+
this.log("Failed to connect");
|
|
167
|
+
// Chnaged the connected state to false
|
|
168
|
+
if (this.connected$.value)
|
|
169
|
+
this.connected$.next(false);
|
|
170
|
+
// Increment the attempts counter
|
|
142
171
|
this.attempts$.next(this.attempts$.value + 1);
|
|
172
|
+
// Reset the state
|
|
143
173
|
this.resetState();
|
|
144
174
|
// Start the reconnect timer if the connection was not closed cleanly
|
|
145
175
|
if (!event.wasClean)
|
|
@@ -165,9 +195,17 @@ export class Relay {
|
|
|
165
195
|
shareReplay(1));
|
|
166
196
|
this.limitations$ = this.information$.pipe(map((info) => (info ? info.limitation : null)));
|
|
167
197
|
this.supported$ = this.information$.pipe(map((info) => info && Array.isArray(info.supported_nips) ? info.supported_nips.filter((n) => typeof n === "number") : null));
|
|
198
|
+
this.icon$ = this.information$.pipe(map((info) => info?.icon || new URL("/favicon.ico", ensureHttpURL(this.url)).toString()));
|
|
168
199
|
// Create observables that track if auth is required for REQ or EVENT
|
|
169
|
-
this.authRequiredForRead$ = this.receivedAuthRequiredForReq
|
|
170
|
-
this.authRequiredForPublish$ = this.receivedAuthRequiredForEvent
|
|
200
|
+
this.authRequiredForRead$ = this.receivedAuthRequiredForReq;
|
|
201
|
+
this.authRequiredForPublish$ = this.receivedAuthRequiredForEvent;
|
|
202
|
+
// Log when auth is required
|
|
203
|
+
this.authRequiredForRead$
|
|
204
|
+
.pipe(filter((r) => r === true), take(1))
|
|
205
|
+
.subscribe(() => this.log("Auth required for REQ"));
|
|
206
|
+
this.authRequiredForPublish$
|
|
207
|
+
.pipe(filter((r) => r === true), take(1))
|
|
208
|
+
.subscribe(() => this.log("Auth required for EVENT"));
|
|
171
209
|
// Update the notices state
|
|
172
210
|
const listenForNotice = this.socket.pipe(
|
|
173
211
|
// listen for NOTICE messages
|
|
@@ -219,13 +257,15 @@ export class Relay {
|
|
|
219
257
|
}
|
|
220
258
|
/** Set ready = false and start the reconnect timer */
|
|
221
259
|
startReconnectTimer(error) {
|
|
222
|
-
if (!this.ready
|
|
260
|
+
if (!this.ready)
|
|
223
261
|
return;
|
|
224
262
|
this.error$.next(error instanceof Error ? error : new Error("Connection error"));
|
|
225
|
-
this.
|
|
263
|
+
this._ready$.next(false);
|
|
226
264
|
this.reconnectTimer(error, this.attempts$.value)
|
|
227
265
|
.pipe(take(1))
|
|
228
|
-
.subscribe(() =>
|
|
266
|
+
.subscribe(() => {
|
|
267
|
+
this._ready$.next(true);
|
|
268
|
+
});
|
|
229
269
|
}
|
|
230
270
|
/** Wait for authentication state, make connection and then wait for authentication if required */
|
|
231
271
|
waitForAuth(
|
|
@@ -244,7 +284,7 @@ export class Relay {
|
|
|
244
284
|
/** Wait for the relay to be ready to accept connections */
|
|
245
285
|
waitForReady(observable) {
|
|
246
286
|
// Don't wait if the relay is already ready
|
|
247
|
-
if (this.ready
|
|
287
|
+
if (this.ready)
|
|
248
288
|
return observable;
|
|
249
289
|
else
|
|
250
290
|
return this.ready$.pipe(
|
|
@@ -320,9 +360,7 @@ export class Relay {
|
|
|
320
360
|
/** Create a COUNT observable that emits a single count response */
|
|
321
361
|
count(filters, id = nanoid()) {
|
|
322
362
|
// 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());
|
|
363
|
+
const messages = this.socket.pipe(filter((m) => Array.isArray(m) && (m[0] === "COUNT" || m[0] === "CLOSED") && m[1] === id));
|
|
326
364
|
// Send the COUNT message and listen for response
|
|
327
365
|
const observable = defer(() => {
|
|
328
366
|
// Send the COUNT message when subscription starts
|
|
@@ -343,11 +381,10 @@ export class Relay {
|
|
|
343
381
|
timeout({
|
|
344
382
|
first: this.eoseTimeout,
|
|
345
383
|
with: () => throwError(() => new Error("COUNT timeout")),
|
|
346
|
-
})
|
|
347
|
-
// Only create one upstream subscription
|
|
348
|
-
share());
|
|
384
|
+
}));
|
|
349
385
|
// Start the watch tower and wait for auth if required
|
|
350
|
-
|
|
386
|
+
// Use share() to prevent multiple subscriptions from creating duplicate COUNT messages
|
|
387
|
+
return this.waitForReady(this.waitForAuth(this.authRequiredForRead$, observable)).pipe(share());
|
|
351
388
|
}
|
|
352
389
|
/** Send an EVENT or AUTH message and return an observable of PublishResponse that completes or errors */
|
|
353
390
|
event(event, verb = "EVENT") {
|
|
@@ -378,14 +415,13 @@ export class Relay {
|
|
|
378
415
|
timeout({
|
|
379
416
|
first: this.eventTimeout,
|
|
380
417
|
with: () => of({ ok: false, from: this.url, message: "Timeout" }),
|
|
381
|
-
})
|
|
382
|
-
// Only create one upstream subscription
|
|
383
|
-
share());
|
|
418
|
+
}));
|
|
384
419
|
// skip wait for auth if verb is AUTH
|
|
420
|
+
// Use share() to prevent multiple subscriptions from creating duplicate EVENT messages
|
|
385
421
|
if (verb === "AUTH")
|
|
386
|
-
return this.waitForReady(observable);
|
|
422
|
+
return this.waitForReady(observable).pipe(share());
|
|
387
423
|
else
|
|
388
|
-
return this.waitForReady(this.waitForAuth(this.authRequiredForPublish$, observable));
|
|
424
|
+
return this.waitForReady(this.waitForAuth(this.authRequiredForPublish$, observable)).pipe(share());
|
|
389
425
|
}
|
|
390
426
|
/** send and AUTH message */
|
|
391
427
|
auth(event) {
|
|
@@ -407,7 +443,7 @@ export class Relay {
|
|
|
407
443
|
authenticate(signer) {
|
|
408
444
|
if (!this.challenge)
|
|
409
445
|
throw new Error("Have not received authentication challenge");
|
|
410
|
-
const p = signer.signEvent(
|
|
446
|
+
const p = signer.signEvent(makeAuthEvent(this.url, this.challenge));
|
|
411
447
|
const start = p instanceof Promise ? from(p) : of(p);
|
|
412
448
|
return lastValueFrom(start.pipe(switchMap((event) => this.auth(event))));
|
|
413
449
|
}
|
|
@@ -463,7 +499,7 @@ export class Relay {
|
|
|
463
499
|
subscription(filters, opts) {
|
|
464
500
|
return this.req(filters, opts?.id).pipe(
|
|
465
501
|
// Retry on connection errors
|
|
466
|
-
this.customRetryOperator(opts?.
|
|
502
|
+
this.customRetryOperator(opts?.reconnect ?? true, this.subscriptionReconnect),
|
|
467
503
|
// Create resubscribe logic (repeat operator)
|
|
468
504
|
this.customRepeatOperator(opts?.resubscribe),
|
|
469
505
|
// Single subscription
|
|
@@ -473,7 +509,7 @@ export class Relay {
|
|
|
473
509
|
request(filters, opts) {
|
|
474
510
|
return this.req(filters, opts?.id).pipe(
|
|
475
511
|
// Retry on connection errors
|
|
476
|
-
this.customRetryOperator(opts?.
|
|
512
|
+
this.customRetryOperator(opts?.reconnect ?? true, this.requestReconnect),
|
|
477
513
|
// Create resubscribe logic (repeat operator)
|
|
478
514
|
this.customRepeatOperator(opts?.resubscribe),
|
|
479
515
|
// Complete when EOSE is received
|
|
@@ -490,11 +526,9 @@ export class Relay {
|
|
|
490
526
|
return of(result);
|
|
491
527
|
}),
|
|
492
528
|
// Retry the publish until it succeeds or the number of retries is reached
|
|
493
|
-
this.customRetryOperator(opts?.retries ?? opts?.reconnect ?? true,
|
|
529
|
+
this.customRetryOperator(opts?.retries ?? opts?.reconnect ?? true, this.publishRetry),
|
|
494
530
|
// Add timeout for publishing
|
|
495
|
-
this.customTimeoutOperator(opts?.timeout, this.publishTimeout)
|
|
496
|
-
// Single subscription
|
|
497
|
-
share()));
|
|
531
|
+
this.customTimeoutOperator(opts?.timeout, this.publishTimeout)));
|
|
498
532
|
}
|
|
499
533
|
/** Negentropy sync events with the relay and an event store */
|
|
500
534
|
sync(store, filter, direction = SyncDirection.RECEIVE) {
|
|
@@ -524,7 +558,15 @@ export class Relay {
|
|
|
524
558
|
}
|
|
525
559
|
// Fetch missing events from the relay
|
|
526
560
|
if (direction & SyncDirection.RECEIVE && need.length > 0) {
|
|
527
|
-
await lastValueFrom(this.req({ ids: need }).pipe(
|
|
561
|
+
await lastValueFrom(this.req({ ids: need }).pipe(
|
|
562
|
+
// Complete when EOSE is received
|
|
563
|
+
completeOnEose(),
|
|
564
|
+
// Add events to the store if its writable
|
|
565
|
+
Reflect.has(store, "add")
|
|
566
|
+
? mapEventsToStore(store)
|
|
567
|
+
: identity,
|
|
568
|
+
// Pass events to observer
|
|
569
|
+
tap((event) => observer.next(event))));
|
|
528
570
|
}
|
|
529
571
|
}, { signal: controller.signal })
|
|
530
572
|
// Complete the observable when the sync is complete
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { IAsyncEventStoreRead, IEventStoreRead } from "applesauce-core";
|
|
2
|
-
import type {
|
|
3
|
-
import type { EventTemplate, NostrEvent } from "
|
|
4
|
-
import type { RelayInformation } from "nostr-tools/nip11";
|
|
1
|
+
import type { IAsyncEventStoreActions, IAsyncEventStoreRead, IEventStoreRead } from "applesauce-core/event-store";
|
|
2
|
+
import type { Filter } from "applesauce-core/helpers/filter";
|
|
3
|
+
import type { EventTemplate, NostrEvent } from "applesauce-core/helpers/event";
|
|
4
|
+
import type { RelayInformation as CoreRelayInformation } from "nostr-tools/nip11";
|
|
5
5
|
import type { Observable, repeat, retry } from "rxjs";
|
|
6
6
|
import type { WebSocketSubject } from "rxjs/webSocket";
|
|
7
7
|
import type { GroupNegentropySyncOptions, GroupRequestOptions, GroupSubscriptionOptions } from "./group.js";
|
|
@@ -19,14 +19,10 @@ export type CountResponse = {
|
|
|
19
19
|
export type MultiplexWebSocket<T = any> = Pick<WebSocketSubject<T>, "multiplex">;
|
|
20
20
|
/** Options for the publish method on the pool and relay */
|
|
21
21
|
export type PublishOptions = {
|
|
22
|
+
/** Number of times to retry the publish. default is 3 */
|
|
23
|
+
retries?: boolean | number | Parameters<typeof retry>[0];
|
|
22
24
|
/**
|
|
23
|
-
*
|
|
24
|
-
* @see https://rxjs.dev/api/index/function/retry
|
|
25
|
-
* @deprecated use `reconnect` instead
|
|
26
|
-
*/
|
|
27
|
-
retries?: number | Parameters<typeof retry>[0];
|
|
28
|
-
/**
|
|
29
|
-
* Whether to reconnect when socket fails to connect. default is true (10 retries with 1 second delay)
|
|
25
|
+
* Whether to reconnect when socket fails to connect. default is true (3 retries with 1 second delay)
|
|
30
26
|
* @see https://rxjs.dev/api/index/function/retry
|
|
31
27
|
*/
|
|
32
28
|
reconnect?: boolean | number | Parameters<typeof retry>[0];
|
|
@@ -39,19 +35,13 @@ export type RequestOptions = SubscriptionOptions;
|
|
|
39
35
|
export type SubscriptionOptions = {
|
|
40
36
|
/** Custom REQ id for the subscription */
|
|
41
37
|
id?: string;
|
|
42
|
-
/**
|
|
43
|
-
* Number of times to retry the subscription if the relay fails to connect. default is 10
|
|
44
|
-
* @see https://rxjs.dev/api/index/function/retry
|
|
45
|
-
* @deprecated use `reconnect` instead
|
|
46
|
-
*/
|
|
47
|
-
retries?: number | Parameters<typeof retry>[0];
|
|
48
38
|
/**
|
|
49
39
|
* Whether to resubscribe if the subscription is closed by the relay. default is false
|
|
50
40
|
* @see https://rxjs.dev/api/index/function/repeat
|
|
51
41
|
*/
|
|
52
42
|
resubscribe?: boolean | number | Parameters<typeof repeat>[0];
|
|
53
43
|
/**
|
|
54
|
-
* Whether to reconnect when socket is closed. default is true (
|
|
44
|
+
* Whether to reconnect when socket is closed. default is true (3 retries with 1 second delay)
|
|
55
45
|
* @see https://rxjs.dev/api/index/function/retry
|
|
56
46
|
*/
|
|
57
47
|
reconnect?: boolean | number | Parameters<typeof retry>[0];
|
|
@@ -60,7 +50,17 @@ export type AuthSigner = {
|
|
|
60
50
|
signEvent: (event: EventTemplate) => NostrEvent | Promise<NostrEvent>;
|
|
61
51
|
};
|
|
62
52
|
/** Filters that can be passed to request methods on the pool or relay */
|
|
63
|
-
export type FilterInput =
|
|
53
|
+
export type FilterInput = Filter | Filter[] | Observable<Filter | Filter[]> | ((relay: IRelay) => Filter | Filter[] | Observable<Filter | Filter[]>);
|
|
54
|
+
export type RelayInformation = CoreRelayInformation & {
|
|
55
|
+
/** An array of attributes that describe the relay type/characteristics */
|
|
56
|
+
attributes?: string[];
|
|
57
|
+
};
|
|
58
|
+
/** A read only event store for negentropy sync */
|
|
59
|
+
export type NegentropyReadStore = IEventStoreRead | IAsyncEventStoreRead | NostrEvent[];
|
|
60
|
+
/** A writeable event store for negentropy sync */
|
|
61
|
+
export type NegentropyWriteStore = (IAsyncEventStoreRead & IAsyncEventStoreActions) | (IEventStoreRead & IAsyncEventStoreActions);
|
|
62
|
+
/** An event store that can be used for negentropy sync */
|
|
63
|
+
export type NegentropySyncStore = NegentropyReadStore | NegentropyWriteStore;
|
|
64
64
|
export interface IRelay extends MultiplexWebSocket {
|
|
65
65
|
url: string;
|
|
66
66
|
message$: Observable<any>;
|
|
@@ -82,13 +82,13 @@ export interface IRelay extends MultiplexWebSocket {
|
|
|
82
82
|
/** Send a REQ message */
|
|
83
83
|
req(filters: FilterInput, id?: string): Observable<SubscriptionResponse>;
|
|
84
84
|
/** Send a COUNT message */
|
|
85
|
-
count(filters:
|
|
85
|
+
count(filters: Filter | Filter[], id?: string): Observable<CountResponse>;
|
|
86
86
|
/** Send an EVENT message */
|
|
87
87
|
event(event: NostrEvent): Observable<PublishResponse>;
|
|
88
88
|
/** Send an AUTH message */
|
|
89
89
|
auth(event: NostrEvent): Promise<PublishResponse>;
|
|
90
90
|
/** Negentropy sync event ids with the relay and an event store */
|
|
91
|
-
negentropy(store:
|
|
91
|
+
negentropy(store: NegentropyReadStore, filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
|
|
92
92
|
/** Authenticate with the relay using a signer */
|
|
93
93
|
authenticate(signer: AuthSigner): Promise<PublishResponse>;
|
|
94
94
|
/** Send an EVENT message with retries */
|
|
@@ -98,7 +98,7 @@ export interface IRelay extends MultiplexWebSocket {
|
|
|
98
98
|
/** Open a subscription with retries */
|
|
99
99
|
subscription(filters: FilterInput, opts?: SubscriptionOptions): Observable<SubscriptionResponse>;
|
|
100
100
|
/** Negentropy sync events with the relay and an event store */
|
|
101
|
-
sync(store:
|
|
101
|
+
sync(store: NegentropySyncStore, filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
|
|
102
102
|
/** Get the NIP-11 information document for the relay */
|
|
103
103
|
getInformation(): Promise<RelayInformation | null>;
|
|
104
104
|
/** Get the limitations for the relay */
|
|
@@ -113,7 +113,7 @@ export interface IGroup {
|
|
|
113
113
|
/** Send an EVENT message */
|
|
114
114
|
event(event: Parameters<IRelay["event"]>[0]): Observable<PublishResponse>;
|
|
115
115
|
/** Negentropy sync event ids with the relays and an event store */
|
|
116
|
-
negentropy(store:
|
|
116
|
+
negentropy(store: NegentropyReadStore, filter: Filter, reconcile: ReconcileFunction, opts?: NegentropySyncOptions): Promise<boolean>;
|
|
117
117
|
/** Add a relay to the group */
|
|
118
118
|
add(relay: IRelay): void;
|
|
119
119
|
/** Remove a relay from the group */
|
|
@@ -127,9 +127,9 @@ export interface IGroup {
|
|
|
127
127
|
/** Open a subscription with retries */
|
|
128
128
|
subscription(filters: Parameters<IRelay["subscription"]>[0], opts?: GroupSubscriptionOptions): Observable<SubscriptionResponse>;
|
|
129
129
|
/** Count events on the relays and an event store */
|
|
130
|
-
count(filters:
|
|
130
|
+
count(filters: Filter | Filter[], id?: string): Observable<Record<string, CountResponse>>;
|
|
131
131
|
/** Negentropy sync events with the relay and an event store */
|
|
132
|
-
sync(store:
|
|
132
|
+
sync(store: NegentropySyncStore, filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
|
|
133
133
|
}
|
|
134
134
|
/** Signals emitted by the pool */
|
|
135
135
|
export interface IPoolSignals {
|
|
@@ -149,7 +149,7 @@ export interface IPool extends IPoolSignals {
|
|
|
149
149
|
/** Send an EVENT message */
|
|
150
150
|
event(relays: IPoolRelayInput, event: NostrEvent): Observable<PublishResponse>;
|
|
151
151
|
/** Negentropy sync event ids with the relays and an event store */
|
|
152
|
-
negentropy(relays: IPoolRelayInput, store:
|
|
152
|
+
negentropy(relays: IPoolRelayInput, store: NegentropyReadStore, filter: Filter, reconcile: ReconcileFunction, opts?: GroupNegentropySyncOptions): Promise<boolean>;
|
|
153
153
|
/** Send an EVENT message to relays with retries */
|
|
154
154
|
publish(relays: IPoolRelayInput, event: Parameters<IGroup["publish"]>[0], opts?: Parameters<IGroup["publish"]>[1]): Promise<PublishResponse[]>;
|
|
155
155
|
/** Send a REQ message to relays with retries */
|
|
@@ -157,7 +157,7 @@ export interface IPool extends IPoolSignals {
|
|
|
157
157
|
/** Open a subscription to relays with retries */
|
|
158
158
|
subscription(relays: IPoolRelayInput, filters: Parameters<IGroup["subscription"]>[0], opts?: Parameters<IGroup["subscription"]>[1]): Observable<SubscriptionResponse>;
|
|
159
159
|
/** Count events on the relays and an event store */
|
|
160
|
-
count(relays: IPoolRelayInput, filters:
|
|
160
|
+
count(relays: IPoolRelayInput, filters: Filter | Filter[], id?: string): Observable<Record<string, CountResponse>>;
|
|
161
161
|
/** Negentropy sync events with the relay and an event store */
|
|
162
|
-
sync(relays: IPoolRelayInput, store:
|
|
162
|
+
sync(relays: IPoolRelayInput, store: NegentropySyncStore, filter: Filter, direction?: SyncDirection): Observable<NostrEvent>;
|
|
163
163
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-relay",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "nostr relay communication framework built on rxjs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
"require": "./dist/pool.js",
|
|
25
25
|
"types": "./dist/pool.d.ts"
|
|
26
26
|
},
|
|
27
|
+
"./group": {
|
|
28
|
+
"import": "./dist/group.js",
|
|
29
|
+
"require": "./dist/group.js",
|
|
30
|
+
"types": "./dist/group.d.ts"
|
|
31
|
+
},
|
|
27
32
|
"./relay": {
|
|
28
33
|
"import": "./dist/relay.js",
|
|
29
34
|
"require": "./dist/relay.js",
|
|
@@ -52,17 +57,17 @@
|
|
|
52
57
|
},
|
|
53
58
|
"dependencies": {
|
|
54
59
|
"@noble/hashes": "^1.7.1",
|
|
55
|
-
"applesauce-core": "^
|
|
60
|
+
"applesauce-core": "^5.0.0",
|
|
56
61
|
"nanoid": "^5.0.9",
|
|
57
|
-
"nostr-tools": "~2.
|
|
62
|
+
"nostr-tools": "~2.19",
|
|
58
63
|
"rxjs": "^7.8.1"
|
|
59
64
|
},
|
|
60
65
|
"devDependencies": {
|
|
61
66
|
"@hirez_io/observer-spy": "^2.2.0",
|
|
62
|
-
"applesauce-signers": "^
|
|
67
|
+
"applesauce-signers": "^5.0.0",
|
|
63
68
|
"rimraf": "^6.0.1",
|
|
64
69
|
"typescript": "^5.7.3",
|
|
65
|
-
"vitest": "^
|
|
70
|
+
"vitest": "^4.0.15",
|
|
66
71
|
"vitest-websocket-mock": "^0.5.0"
|
|
67
72
|
},
|
|
68
73
|
"funding": {
|