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

View File

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

View File

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