rx-nostr
Version:
A library based on RxJS, which allows Nostr applications to easily communicate with relays.
128 lines (106 loc) • 3.13 kB
text/typescript
import * as Nostr from "nostr-typedef";
import { Observable, Subject } from "rxjs";
import { OkPacketAgainstEvent } from "../packet.js";
import { AuthProxy } from "./auth.js";
import { RelayConnection } from "./relay.js";
import { CounterSubject } from "./utils.js";
export class PublishProxy {
private relay: RelayConnection;
private authProxy: AuthProxy | null;
private pubs = new Map<string, Nostr.Event>();
private authRequiredPubs = new Set<string>();
private count$ = new CounterSubject(0);
private ok$ = new Subject<OkPacketAgainstEvent>();
private disposed = false;
constructor(params: { relay: RelayConnection; authProxy: AuthProxy | null }) {
this.relay = params.relay;
this.authProxy = params.authProxy;
// Recovering
this.relay.getReconnectedObservable().subscribe((toRelayMessage) => {
for (const [type, event] of toRelayMessage) {
if (type !== "EVENT") {
continue;
}
if (this.pubs.has(event.id)) {
this.sendEVENT(event);
}
}
});
// Handle OKs.
// If auth is required and it can be done,
// set a flag on OkPacket and try to resend EVENT after done.
this.relay.getOKObservable().subscribe(async (packet) => {
const { eventId, notice } = packet;
const event = this.pubs.get(eventId);
if (!event) {
return;
}
if (this.authProxy && notice?.startsWith("auth-required:")) {
this.authRequiredPubs.add(eventId);
this.ok$.next({ ...packet, done: false });
} else {
this.ok$.next({
...packet,
done: true,
});
this.confirmOK(eventId);
}
});
this.authProxy?.getAuthResultObservable().subscribe((ok) => {
if (ok) {
for (const eventId of this.authRequiredPubs) {
const event = this.pubs.get(eventId);
if (event) {
this.sendEVENT(event);
}
}
} else {
for (const eventId of this.authRequiredPubs) {
this.confirmOK(eventId);
}
}
this.authRequiredPubs.clear();
});
}
async publish(event: Nostr.Event): Promise<void> {
if (this.disposed) {
return;
}
if (!this.pubs.has(event.id)) {
this.pubs.set(event.id, event);
this.count$.increment();
}
return this.sendEVENT(event);
}
confirmOK(eventId: string): void {
if (this.disposed) {
return;
}
if (!this.pubs.has(eventId)) {
this.pubs.delete(eventId);
this.count$.decrement();
}
}
getOkAgainstEventObservable(): Observable<OkPacketAgainstEvent> {
return this.ok$.asObservable();
}
getLogicalConnectionSizeObservable(): Observable<number> {
return this.count$.asObservable();
}
dispose() {
this[Symbol.dispose]();
}
[Symbol.dispose](): void {
if (this.disposed) {
return;
}
this.disposed = true;
const subjects = [this.count$, this.ok$];
for (const sub of subjects) {
sub.complete();
}
}
private sendEVENT(event: Nostr.Event) {
return this.relay.send(["EVENT", event]);
}
}