Merge pull request #1614 from RoboSats/refactor-nostr-websockets

Refactor nostr websocket to be preared for mobile
This commit is contained in:
KoalaSat 2024-11-12 09:05:44 +00:00 committed by GitHub
commit 02ac9e59dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 107 additions and 96 deletions

View File

@ -127,10 +127,13 @@ const EncryptedSocketChat: React.FC<Props> = ({
setConnection(connection); setConnection(connection);
setConnected(true); setConnected(true);
connection.send({ connection.send(
message: robot?.pubKey, JSON.stringify({
nick: userNick, type: 'message',
}); message: robot?.pubKey,
nick: userNick,
}),
);
connection.onMessage((message) => { connection.onMessage((message) => {
setServerMessages((prev) => [...prev, message]); setServerMessages((prev) => [...prev, message]);
@ -173,10 +176,13 @@ const EncryptedSocketChat: React.FC<Props> = ({
dataFromServer.message !== robot.pubKey dataFromServer.message !== robot.pubKey
) { ) {
setPeerPubKey(dataFromServer.message); setPeerPubKey(dataFromServer.message);
connection.send({ connection.send(
message: `-----SERVE HISTORY-----`, JSON.stringify({
nick: userNick, type: 'message',
}); message: `-----SERVE HISTORY-----`,
nick: userNick,
}),
);
} }
// If we receive an encrypted message // If we receive an encrypted message
else if (dataFromServer.message.substring(0, 27) === `-----BEGIN PGP MESSAGE-----`) { else if (dataFromServer.message.substring(0, 27) === `-----BEGIN PGP MESSAGE-----`) {
@ -242,10 +248,13 @@ const EncryptedSocketChat: React.FC<Props> = ({
} }
// If input string contains '#' send unencrypted and unlogged message // If input string contains '#' send unencrypted and unlogged message
else if (connection != null && value.substring(0, 1) === '#') { else if (connection != null && value.substring(0, 1) === '#') {
connection.send({ connection.send(
message: value, JSON.stringify({
nick: userNick, type: 'message',
}); message: value,
nick: userNick,
}),
);
setValue(''); setValue('');
} }
@ -257,10 +266,13 @@ const EncryptedSocketChat: React.FC<Props> = ({
encryptMessage(value, robot.pubKey, peerPubKey, robot.encPrivKey, slot.token) encryptMessage(value, robot.pubKey, peerPubKey, robot.encPrivKey, slot.token)
.then((encryptedMessage) => { .then((encryptedMessage) => {
if (connection != null) { if (connection != null) {
connection.send({ connection.send(
message: String(encryptedMessage).split('\n').join('\\'), JSON.stringify({
nick: userNick, type: 'message',
}); message: String(encryptedMessage).split('\n').join('\\'),
nick: userNick,
}),
);
} }
}) })
.catch((error) => { .catch((error) => {

View File

@ -1,6 +1,7 @@
import { type Event } from 'nostr-tools'; import { type Event } from 'nostr-tools';
import { type Settings } from '../../models'; import { type Settings } from '../../models';
import defaultFederation from '../../../static/federation.json'; import defaultFederation from '../../../static/federation.json';
import { websocketClient, WebsocketConnection, WebsocketState } from '../Websocket';
interface RoboPoolEvents { interface RoboPoolEvents {
onevent: (event: Event) => void; onevent: (event: Event) => void;
@ -39,63 +40,60 @@ class RoboPool {
public relays: string[]; public relays: string[];
public network: string; public network: string;
public webSockets: WebSocket[] = []; public webSockets: Record<string, WebsocketConnection | null> = {};
private readonly messageHandlers: Array<(url: string, event: MessageEvent) => void> = []; private readonly messageHandlers: Array<(url: string, event: MessageEvent) => void> = [];
connect = (): void => { connect = (): void => {
this.relays.forEach((url) => { this.relays.forEach((url: string) => {
if (this.webSockets.find((w: WebSocket) => w.url === url)) return; if (Object.keys(this.webSockets).find((wUrl) => wUrl === url)) return;
let ws: WebSocket; this.webSockets[url] = null;
const connect = (): void => { const connectRelay = () => {
ws = new WebSocket(url); websocketClient.open(url).then((connection) => {
// Add event listeners for the WebSocket
ws.onopen = () => {
console.log(`Connected to ${url}`); console.log(`Connected to ${url}`);
};
ws.onmessage = (event) => { connection.onMessage((event) => {
this.messageHandlers.forEach((handler) => { this.messageHandlers.forEach((handler) => {
handler(url, event); handler(url, event);
});
}); });
};
ws.onerror = (error) => { connection.onError((error) => {
console.error(`WebSocket error on ${url}:`, error); console.error(`WebSocket error on ${url}:`, error);
}; });
ws.onclose = () => { connection.onClose(() => {
console.log(`Disconnected from ${url}. Attempting to reconnect...`); console.log(`Disconnected from ${url}`);
setTimeout(connect, 1000); // Reconnect after 1 second });
};
this.webSockets[url] = connection;
});
}; };
connectRelay();
connect();
this.webSockets.push(ws);
}); });
}; };
close = (): void => { close = (): void => {
this.webSockets.forEach((ws) => { Object.values(this.webSockets).forEach((ws) => {
ws.close(); ws?.close();
}); });
this.webSockets = {};
}; };
sendMessage = (message: string): void => { sendMessage = (message: string): void => {
const send = (index: number, message: string): void => { const send = (url: string, message: string): void => {
const ws = this.webSockets[index]; const ws = this.webSockets[url];
if (ws.readyState === WebSocket.OPEN) { if (!ws || ws.getReadyState() === WebsocketState.CONNECTING) {
setTimeout(send, 500, url, message);
} else if (ws.getReadyState() === WebsocketState.OPEN) {
ws.send(message); ws.send(message);
} else if (ws.readyState === WebSocket.CONNECTING) {
setTimeout(send, 500, index, message);
} }
}; };
this.webSockets.forEach((_ws, index) => { Object.keys(this.webSockets).forEach((url) => {
send(index, message); send(url, message);
}); });
}; };

View File

@ -1,44 +0,0 @@
import ReconnectingWebSocket from 'reconnecting-websocket';
import { type WebsocketConnection } from '..';
class WebsocketConnectionWeb implements WebsocketConnection {
constructor(path: string) {
this.rws = new ReconnectingWebSocket(path, [], {
WebSocket,
minReconnectionDelay: 15000,
connectionTimeout: 15000,
reconnectionDelayGrowFactor: 2,
maxRetries: 4,
maxReconnectionDelay: 1000000,
});
}
public rws: ReconnectingWebSocket;
public send: (message: object) => void = (message: object) => {
this.rws.send(
JSON.stringify({
type: 'message',
...message,
}),
);
};
public close: () => void = () => {
this.rws.close();
};
public onMessage: (event: (message: any) => void) => void = (event) => {
this.rws.addEventListener('message', event);
};
public onClose: (event: () => void) => void = (event) => {
this.rws.addEventListener('close', event);
};
public onError: (event: () => void) => void = (event) => {
this.rws.addEventListener('error', event);
};
}
export default WebsocketConnectionWeb;

View File

@ -1,5 +1,42 @@
import ReconnectingWebSocket from 'reconnecting-websocket';
import { type WebsocketClient, type WebsocketConnection } from '..'; import { type WebsocketClient, type WebsocketConnection } from '..';
import WebsocketConnectionWeb from '../WebsocketConnectionWeb';
class WebsocketConnectionWeb implements WebsocketConnection {
constructor(path: string) {
this.rws = new ReconnectingWebSocket(path, [], {
WebSocket,
minReconnectionDelay: 15000,
connectionTimeout: 15000,
reconnectionDelayGrowFactor: 2,
maxRetries: 4,
maxReconnectionDelay: 1000000,
});
}
public rws: ReconnectingWebSocket;
public send: (message: string) => void = (message: string) => {
this.rws.send(message);
};
public close: () => void = () => {
this.rws.close();
};
public onMessage: (event: (message: any) => void) => void = (event) => {
this.rws.addEventListener('message', event);
};
public onClose: (event: () => void) => void = (event) => {
this.rws.addEventListener('close', event);
};
public onError: (event: (error: any) => void) => void = (event) => {
this.rws.addEventListener('error', event);
};
public getReadyState: () => number = () => this.rws.readyState;
}
class WebsocketWebClient implements WebsocketClient { class WebsocketWebClient implements WebsocketClient {
public open: (path: string) => Promise<WebsocketConnection> = async (path) => { public open: (path: string) => Promise<WebsocketConnection> = async (path) => {

View File

@ -1,11 +1,19 @@
import WebsocketWebClient from './WebsocketWebClient'; import WebsocketWebClient from './WebsocketWebClient';
export const WebsocketState = {
CONNECTING: 0,
OPEN: 1,
CLOSING: 2,
CLOSED: 3,
} as const;
export interface WebsocketConnection { export interface WebsocketConnection {
send: (message: object) => void; send: (message: string) => void;
onMessage: (event: (message: any) => void) => void; onMessage: (event: (message: any) => void) => void;
onClose: (event: () => void) => void; onClose: (event: () => void) => void;
onError: (event: () => void) => void; onError: (event: (error: any) => void) => void;
close: () => void; close: () => void;
getReadyState: () => number;
} }
export interface WebsocketClient { export interface WebsocketClient {