applesauce-relay 0.0.0-next-20250327153627 → 0.0.0-next-20250330150216
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 +10 -0
- package/dist/group.js +23 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/operators/mark-from-relay.d.ts +1 -1
- package/dist/operators/only-events.d.ts +1 -1
- package/dist/pool.d.ts +11 -4
- package/dist/pool.js +21 -23
- package/dist/relay.d.ts +6 -10
- package/dist/relay.js +17 -9
- package/dist/types.d.ts +36 -0
- package/dist/types.js +1 -0
- package/package.json +2 -2
package/dist/group.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { Observable } from "rxjs";
|
|
3
|
+
import { Filter } from "nostr-tools";
|
|
4
|
+
import { IRelay, Nip01Actions, PublishResponse, SubscriptionResponse } from "./types.js";
|
|
5
|
+
export declare class RelayGroup implements Nip01Actions {
|
|
6
|
+
relays: IRelay[];
|
|
7
|
+
constructor(relays: IRelay[]);
|
|
8
|
+
req(filters: Filter | Filter[], id?: string): Observable<SubscriptionResponse>;
|
|
9
|
+
event(event: NostrEvent): Observable<PublishResponse>;
|
|
10
|
+
}
|
package/dist/group.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { combineLatest, filter, map, merge } from "rxjs";
|
|
2
|
+
import { onlyEvents } from "./operators/only-events.js";
|
|
3
|
+
import { nanoid } from "nanoid";
|
|
4
|
+
export class RelayGroup {
|
|
5
|
+
relays;
|
|
6
|
+
constructor(relays) {
|
|
7
|
+
this.relays = relays;
|
|
8
|
+
}
|
|
9
|
+
req(filters, id = nanoid()) {
|
|
10
|
+
const requests = this.relays.reduce((acc, r) => ({ ...acc, [r.url]: r.req(filters, id) }), {});
|
|
11
|
+
// Create stream of events only
|
|
12
|
+
const events = merge(...Object.values(requests)).pipe(onlyEvents());
|
|
13
|
+
// Create stream that emits EOSE when all relays have sent EOSE
|
|
14
|
+
const eose = combineLatest(
|
|
15
|
+
// Create a new map of requests that only emits EOSE
|
|
16
|
+
Object.fromEntries(Object.entries(requests).map(([url, observable]) => [url, observable.pipe(filter((m) => m === "EOSE"))]))).pipe(map(() => "EOSE"));
|
|
17
|
+
// Merge events and the single EOSE stream
|
|
18
|
+
return merge(events, eose);
|
|
19
|
+
}
|
|
20
|
+
event(event) {
|
|
21
|
+
return merge(...this.relays.map((r) => r.event(event)));
|
|
22
|
+
}
|
|
23
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { MonoTypeOperatorFunction } from "rxjs";
|
|
2
|
-
import { SubscriptionResponse } from "../
|
|
2
|
+
import { SubscriptionResponse } from "../types.js";
|
|
3
3
|
/** Marks all events as from the relay */
|
|
4
4
|
export declare function markFromRelay(relay: string): MonoTypeOperatorFunction<SubscriptionResponse>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OperatorFunction } from "rxjs";
|
|
2
2
|
import { NostrEvent } from "nostr-tools";
|
|
3
|
-
import { SubscriptionResponse } from "../
|
|
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>;
|
package/dist/pool.d.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { NostrEvent, type Filter } from "nostr-tools";
|
|
2
2
|
import { Observable } from "rxjs";
|
|
3
|
-
import { Relay,
|
|
3
|
+
import { Relay, RelayOptions } from "./relay.js";
|
|
4
|
+
import { PublishResponse, SubscriptionResponse } from "./types.js";
|
|
5
|
+
import { RelayGroup } from "./group.js";
|
|
4
6
|
export declare class RelayPool {
|
|
7
|
+
options?: RelayOptions | undefined;
|
|
5
8
|
relays: Map<string, Relay>;
|
|
9
|
+
groups: Map<string, RelayGroup>;
|
|
10
|
+
constructor(options?: RelayOptions | undefined);
|
|
6
11
|
/** Get or create a new relay connection */
|
|
7
12
|
relay(url: string): Relay;
|
|
8
|
-
/**
|
|
9
|
-
|
|
13
|
+
/** Create a group of relays */
|
|
14
|
+
group(relays: string[]): RelayGroup;
|
|
15
|
+
/** Make a REQ to multiple relays that does not deduplicate events */
|
|
16
|
+
req(relays: string[], filters: Filter | Filter[], id?: string): Observable<SubscriptionResponse>;
|
|
10
17
|
/** Send an EVENT message to multiple relays */
|
|
11
|
-
event(relays: string[], event: NostrEvent): Observable<PublishResponse
|
|
18
|
+
event(relays: string[], event: NostrEvent): Observable<PublishResponse>;
|
|
12
19
|
}
|
package/dist/pool.js
CHANGED
|
@@ -1,41 +1,39 @@
|
|
|
1
|
-
import { combineLatest, endWith, ignoreElements, merge, takeWhile } from "rxjs";
|
|
2
|
-
import { nanoid } from "nanoid";
|
|
3
1
|
import { Relay } from "./relay.js";
|
|
4
|
-
import {
|
|
2
|
+
import { RelayGroup } from "./group.js";
|
|
5
3
|
export class RelayPool {
|
|
4
|
+
options;
|
|
6
5
|
relays = new Map();
|
|
6
|
+
groups = new Map();
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
7
10
|
/** Get or create a new relay connection */
|
|
8
11
|
relay(url) {
|
|
9
12
|
let relay = this.relays.get(url);
|
|
10
13
|
if (relay)
|
|
11
14
|
return relay;
|
|
12
15
|
else {
|
|
13
|
-
relay = new Relay(url);
|
|
16
|
+
relay = new Relay(url, this.options);
|
|
14
17
|
this.relays.set(url, relay);
|
|
15
18
|
return relay;
|
|
16
19
|
}
|
|
17
20
|
}
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
endWith("EOSE"));
|
|
32
|
-
// create a stream that only emits events
|
|
33
|
-
const events = merge(...requests).pipe(onlyEvents());
|
|
34
|
-
// merge events and single EOSE streams
|
|
35
|
-
return merge(events, eose);
|
|
21
|
+
/** Create a group of relays */
|
|
22
|
+
group(relays) {
|
|
23
|
+
const key = relays.sort().join(",");
|
|
24
|
+
let group = this.groups.get(key);
|
|
25
|
+
if (group)
|
|
26
|
+
return group;
|
|
27
|
+
group = new RelayGroup(relays.map((url) => this.relay(url)));
|
|
28
|
+
this.groups.set(key, group);
|
|
29
|
+
return group;
|
|
30
|
+
}
|
|
31
|
+
/** Make a REQ to multiple relays that does not deduplicate events */
|
|
32
|
+
req(relays, filters, id) {
|
|
33
|
+
return this.group(relays).req(filters, id);
|
|
36
34
|
}
|
|
37
35
|
/** Send an EVENT message to multiple relays */
|
|
38
36
|
event(relays, event) {
|
|
39
|
-
return
|
|
37
|
+
return this.group(relays).event(event);
|
|
40
38
|
}
|
|
41
39
|
}
|
package/dist/relay.d.ts
CHANGED
|
@@ -2,29 +2,25 @@ import { BehaviorSubject, Observable } from "rxjs";
|
|
|
2
2
|
import { WebSocketSubject, WebSocketSubjectConfig } from "rxjs/webSocket";
|
|
3
3
|
import { type Filter, type NostrEvent } from "nostr-tools";
|
|
4
4
|
import { logger } from "applesauce-core";
|
|
5
|
-
|
|
6
|
-
export type PublishResponse = {
|
|
7
|
-
ok: boolean;
|
|
8
|
-
message?: string;
|
|
9
|
-
from: string;
|
|
10
|
-
};
|
|
5
|
+
import { IRelay, PublishResponse, SubscriptionResponse } from "./types.js";
|
|
11
6
|
export type RelayOptions = {
|
|
12
7
|
WebSocket?: WebSocketSubjectConfig<any>["WebSocketCtor"];
|
|
13
8
|
};
|
|
14
|
-
export declare class Relay {
|
|
9
|
+
export declare class Relay implements IRelay {
|
|
15
10
|
url: string;
|
|
16
|
-
log: typeof logger;
|
|
11
|
+
protected log: typeof logger;
|
|
17
12
|
socket$: WebSocketSubject<any>;
|
|
18
13
|
connected$: BehaviorSubject<boolean>;
|
|
19
14
|
challenge$: Observable<string>;
|
|
20
15
|
authenticated$: BehaviorSubject<boolean>;
|
|
21
|
-
|
|
16
|
+
notice$: Observable<string>;
|
|
22
17
|
protected authRequiredForReq: BehaviorSubject<boolean>;
|
|
23
18
|
protected authRequiredForPublish: BehaviorSubject<boolean>;
|
|
24
19
|
protected reset(): void;
|
|
25
20
|
constructor(url: string, opts?: RelayOptions);
|
|
26
21
|
protected waitForAuth<T extends unknown = unknown>(requireAuth: Observable<boolean>, observable: Observable<T>): Observable<T>;
|
|
27
|
-
|
|
22
|
+
multiplex<T>(open: () => any, close: () => any, filter: (message: any) => boolean): Observable<T>;
|
|
23
|
+
req(filters: Filter | Filter[], id?: string): Observable<SubscriptionResponse>;
|
|
28
24
|
/** send an Event message */
|
|
29
25
|
event(event: NostrEvent, verb?: "EVENT" | "AUTH"): Observable<PublishResponse>;
|
|
30
26
|
/** send and AUTH message */
|
package/dist/relay.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
import { BehaviorSubject, combineLatest,
|
|
1
|
+
import { BehaviorSubject, combineLatest, filter, map, merge, NEVER, of, shareReplay, switchMap, take, takeWhile, tap, timeout, } from "rxjs";
|
|
2
2
|
import { webSocket } from "rxjs/webSocket";
|
|
3
3
|
import { nanoid } from "nanoid";
|
|
4
4
|
import { logger } from "applesauce-core";
|
|
5
5
|
import { markFromRelay } from "./operators/mark-from-relay.js";
|
|
6
6
|
export class Relay {
|
|
7
7
|
url;
|
|
8
|
-
log = logger.extend("
|
|
8
|
+
log = logger.extend("Relay");
|
|
9
9
|
socket$;
|
|
10
10
|
connected$ = new BehaviorSubject(false);
|
|
11
11
|
challenge$;
|
|
12
12
|
authenticated$ = new BehaviorSubject(false);
|
|
13
|
-
|
|
13
|
+
notice$;
|
|
14
14
|
authRequiredForReq = new BehaviorSubject(false);
|
|
15
15
|
authRequiredForPublish = new BehaviorSubject(false);
|
|
16
16
|
reset() {
|
|
17
|
-
this
|
|
18
|
-
this.
|
|
19
|
-
|
|
17
|
+
// NOTE: only update the values if they need to be changed, otherwise this will cause an infinite loop
|
|
18
|
+
if (this.authenticated$.value)
|
|
19
|
+
this.authenticated$.next(false);
|
|
20
|
+
if (this.authRequiredForReq.value)
|
|
21
|
+
this.authRequiredForReq.next(false);
|
|
22
|
+
if (this.authRequiredForPublish.value)
|
|
23
|
+
this.authRequiredForPublish.next(false);
|
|
20
24
|
}
|
|
21
25
|
constructor(url, opts) {
|
|
22
26
|
this.url = url;
|
|
@@ -47,7 +51,7 @@ export class Relay {
|
|
|
47
51
|
map((m) => m[1]),
|
|
48
52
|
// cache and share the challenge
|
|
49
53
|
shareReplay(1));
|
|
50
|
-
this.
|
|
54
|
+
this.notice$ = this.socket$.pipe(
|
|
51
55
|
// listen for NOTICE messages
|
|
52
56
|
filter((m) => m[0] === "NOTICE"),
|
|
53
57
|
// pick the string out of the message
|
|
@@ -58,14 +62,17 @@ export class Relay {
|
|
|
58
62
|
// return EMPTY if auth is required and not authenticated
|
|
59
63
|
switchMap(([required, authenticated]) => {
|
|
60
64
|
if (required && !authenticated)
|
|
61
|
-
return
|
|
65
|
+
return NEVER;
|
|
62
66
|
else
|
|
63
67
|
return observable;
|
|
64
68
|
}));
|
|
65
69
|
}
|
|
70
|
+
multiplex(open, close, filter) {
|
|
71
|
+
return this.socket$.multiplex(open, close, filter);
|
|
72
|
+
}
|
|
66
73
|
req(filters, id = nanoid()) {
|
|
67
74
|
return this.waitForAuth(this.authRequiredForReq, this.socket$
|
|
68
|
-
.multiplex(() => ["REQ", id, ...filters], () => ["CLOSE", id], (message) => (message[0] === "EVENT" || message[0] === "CLOSE" || message[0] === "EOSE") && message[1] === id)
|
|
75
|
+
.multiplex(() => (Array.isArray(filters) ? ["REQ", id, ...filters] : ["REQ", id, filters]), () => ["CLOSE", id], (message) => (message[0] === "EVENT" || message[0] === "CLOSE" || message[0] === "EOSE") && message[1] === id)
|
|
69
76
|
.pipe(
|
|
70
77
|
// listen for CLOSE auth-required
|
|
71
78
|
tap((m) => {
|
|
@@ -85,6 +92,7 @@ export class Relay {
|
|
|
85
92
|
// mark events as from relays
|
|
86
93
|
markFromRelay(this.url),
|
|
87
94
|
// if no events are seen in 10s, emit EOSE
|
|
95
|
+
// TODO: this should emit EOSE event if events are seen, the timeout should be for only the EOSE message
|
|
88
96
|
timeout({
|
|
89
97
|
first: 10_000,
|
|
90
98
|
with: () => merge(of("EOSE"), NEVER),
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
|
+
import { Observable } from "rxjs";
|
|
3
|
+
import { WebSocketSubject } from "rxjs/webSocket";
|
|
4
|
+
export type SubscriptionResponse = "EOSE" | NostrEvent;
|
|
5
|
+
export type PublishResponse = {
|
|
6
|
+
ok: boolean;
|
|
7
|
+
message?: string;
|
|
8
|
+
from: string;
|
|
9
|
+
};
|
|
10
|
+
export type MultiplexWebSocket<T = any> = Pick<WebSocketSubject<T>, "multiplex">;
|
|
11
|
+
export interface IRelayState {
|
|
12
|
+
connected$: Observable<boolean>;
|
|
13
|
+
challenge$: Observable<string>;
|
|
14
|
+
authenticated$: Observable<boolean>;
|
|
15
|
+
notice$: Observable<string>;
|
|
16
|
+
}
|
|
17
|
+
export interface Nip01Actions {
|
|
18
|
+
/** Send an EVENT message */
|
|
19
|
+
event(event: NostrEvent): Observable<PublishResponse>;
|
|
20
|
+
/** Send a REQ message */
|
|
21
|
+
req(filters: Filter | Filter[], id?: string): Observable<SubscriptionResponse>;
|
|
22
|
+
}
|
|
23
|
+
export interface IRelay extends MultiplexWebSocket, Nip01Actions, IRelayState {
|
|
24
|
+
url: string;
|
|
25
|
+
/** Send an AUTH message */
|
|
26
|
+
auth(event: NostrEvent): Observable<{
|
|
27
|
+
ok: boolean;
|
|
28
|
+
message?: string;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
export interface IPoolActions {
|
|
32
|
+
/** Send an EVENT message */
|
|
33
|
+
event(relays: string[], event: NostrEvent): Observable<PublishResponse[]>;
|
|
34
|
+
/** Send a REQ message */
|
|
35
|
+
req(relays: string[], filters: Filter | Filter[]): Observable<SubscriptionResponse[]>;
|
|
36
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-relay",
|
|
3
|
-
"version": "0.0.0-next-
|
|
3
|
+
"version": "0.0.0-next-20250330150216",
|
|
4
4
|
"description": "A collection of observable based loaders built on rx-nostr",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"applesauce-core": "0.0.0-next-
|
|
36
|
+
"applesauce-core": "0.0.0-next-20250330150216",
|
|
37
37
|
"nanoid": "^5.0.9",
|
|
38
38
|
"nostr-tools": "^2.10.4",
|
|
39
39
|
"rxjs": "^7.8.1"
|