mirror of
https://github.com/RoboSats/robosats.git
synced 2025-07-23 03:33:27 +00:00
178 lines
5.4 KiB
TypeScript
178 lines
5.4 KiB
TypeScript
import { type Event } from 'nostr-tools';
|
|
import { type Coordinator, type Settings } from '../../models';
|
|
import defaultFederation from '../../../static/federation.json';
|
|
import { websocketClient, type WebsocketConnection, WebsocketState } from '../Websocket';
|
|
import thirdParties from '../../../static/thirdparties.json';
|
|
|
|
interface RoboPoolEvents {
|
|
onevent: (event: Event) => void;
|
|
oneose: () => void;
|
|
}
|
|
|
|
class RoboPool {
|
|
constructor(settings: Settings, hostUrl: string, coordinators: Coordinator[]) {
|
|
this.network = settings.network ?? 'mainnet';
|
|
|
|
this.relays = [];
|
|
const federationRelays = coordinators.map((coord) =>
|
|
coord.getRelayUrl(settings.network, hostUrl, settings.selfhostedClient),
|
|
);
|
|
|
|
if (settings.host) {
|
|
const hostNostr = `ws://${settings.host.replace(/^https?:\/\//, '')}/relay`;
|
|
if (federationRelays.includes(hostNostr)) {
|
|
this.relays.push(hostNostr);
|
|
}
|
|
}
|
|
|
|
while (this.relays.length < 3) {
|
|
const randomRelay =
|
|
federationRelays[Math.floor(Math.random() * Object.keys(federationRelays).length)];
|
|
if (!this.relays.includes(randomRelay)) {
|
|
this.relays.push(randomRelay);
|
|
}
|
|
}
|
|
}
|
|
|
|
public relays: string[];
|
|
public network: string;
|
|
|
|
public webSockets: Record<string, WebsocketConnection | null> = {};
|
|
private readonly messageHandlers: Array<(url: string, event: MessageEvent) => void> = [];
|
|
|
|
connect = (relays: string[] = this.relays): void => {
|
|
relays.forEach((url: string) => {
|
|
if (Object.keys(this.webSockets).find((wUrl) => wUrl === url)) return;
|
|
|
|
this.webSockets[url] = null;
|
|
|
|
const connectRelay = (): void => {
|
|
void websocketClient.open(url).then((connection) => {
|
|
console.log(`Connected to ${url}`);
|
|
|
|
connection.onMessage((event) => {
|
|
this.messageHandlers.forEach((handler) => {
|
|
handler(url, event);
|
|
});
|
|
});
|
|
|
|
connection.onError((error) => {
|
|
console.error(`WebSocket error on ${url}:`, error);
|
|
});
|
|
|
|
connection.onClose(() => {
|
|
console.log(`Disconnected from ${url}`);
|
|
});
|
|
|
|
this.webSockets[url] = connection;
|
|
});
|
|
};
|
|
connectRelay();
|
|
});
|
|
};
|
|
|
|
close = (): void => {
|
|
Object.values(this.webSockets).forEach((ws) => {
|
|
ws?.close();
|
|
});
|
|
this.webSockets = {};
|
|
};
|
|
|
|
sendMessage = (message: string): void => {
|
|
const send = (url: string, message: string): void => {
|
|
const ws = this.webSockets[url];
|
|
|
|
if (!ws || ws.getReadyState() === WebsocketState.CONNECTING) {
|
|
setTimeout(send, 500, url, message);
|
|
} else if (ws.getReadyState() === WebsocketState.OPEN) {
|
|
ws.send(message);
|
|
}
|
|
};
|
|
|
|
Object.keys(this.webSockets).forEach((url) => {
|
|
send(url, message);
|
|
});
|
|
};
|
|
|
|
subscribeBook = (robosatsOnly: boolean, events: RoboPoolEvents): void => {
|
|
let scope = Object.values(defaultFederation);
|
|
if (!robosatsOnly) {
|
|
scope = [...scope, ...Object.values(thirdParties)];
|
|
}
|
|
const authors = scope.map((f) => f.nostrHexPubkey).filter((item) => item !== undefined);
|
|
|
|
const requestPending = [
|
|
'REQ',
|
|
'subscribeBookPending',
|
|
{ authors, kinds: [38383], '#s': ['pending'] },
|
|
];
|
|
const requestSuccess = [
|
|
'REQ',
|
|
'subscribeBookSuccess',
|
|
{
|
|
authors,
|
|
kinds: [38383],
|
|
'#s': ['success', 'canceled', 'in-progress'],
|
|
since: Math.floor(new Date().getTime() / 1000),
|
|
},
|
|
];
|
|
|
|
this.messageHandlers.push((_url: string, messageEvent: MessageEvent) => {
|
|
const jsonMessage = JSON.parse(messageEvent.data);
|
|
if (jsonMessage[0] === 'EVENT') {
|
|
const event: Event = jsonMessage[2];
|
|
const network = event.tags.find((e) => e[0] === 'network');
|
|
if (network?.[1] === this.network) events.onevent(jsonMessage[2]);
|
|
} else if (jsonMessage[0] === 'EOSE') {
|
|
events.oneose();
|
|
}
|
|
});
|
|
this.sendMessage(JSON.stringify(requestPending));
|
|
this.sendMessage(JSON.stringify(requestSuccess));
|
|
};
|
|
|
|
subscribeRatings = (events: RoboPoolEvents): void => {
|
|
const pubkeys = Object.values(defaultFederation)
|
|
.map((f) => f.nostrHexPubkey)
|
|
.filter((item) => item !== undefined);
|
|
|
|
const requestRatings = [
|
|
'REQ',
|
|
'subscribeRatings',
|
|
{ kinds: [31986], '#p': pubkeys, since: 1746316800 },
|
|
];
|
|
|
|
this.messageHandlers.push((_url: string, messageEvent: MessageEvent) => {
|
|
const jsonMessage = JSON.parse(messageEvent.data);
|
|
if (jsonMessage[0] === 'EVENT') {
|
|
events.onevent(jsonMessage[2]);
|
|
} else if (jsonMessage[0] === 'EOSE') {
|
|
events.oneose();
|
|
}
|
|
});
|
|
this.sendMessage(JSON.stringify(requestRatings));
|
|
};
|
|
|
|
subscribeChat = (hexPubKeys: string[], since: number, events: RoboPoolEvents): void => {
|
|
const requestRatings = ['REQ', 'subscribeChat', { kinds: [1059], '#p': hexPubKeys, since }];
|
|
|
|
this.messageHandlers.push((_url: string, messageEvent: MessageEvent) => {
|
|
const jsonMessage = JSON.parse(messageEvent.data);
|
|
if (jsonMessage[0] === 'EVENT') {
|
|
events.onevent(jsonMessage[2]);
|
|
} else if (jsonMessage[0] === 'EOSE') {
|
|
events.oneose();
|
|
}
|
|
});
|
|
this.sendMessage(JSON.stringify(requestRatings));
|
|
};
|
|
|
|
sendEvent = (event: Event): void => {
|
|
const message = ['EVENT', event];
|
|
|
|
this.sendMessage(JSON.stringify(message));
|
|
};
|
|
}
|
|
|
|
export default RoboPool;
|