Fix testnet connections

This commit is contained in:
koalasat
2025-05-23 11:50:35 +02:00
parent e612c0f83f
commit 795ed08c24
11 changed files with 79 additions and 123 deletions

View File

@ -20,7 +20,6 @@ import {
type UseFederationStoreType,
FederationContext,
} from '../../../../contexts/FederationContext';
import { type UseAppStoreType, AppContext } from '../../../../contexts/AppContext';
const audioPath =
window.NativeRobosats === undefined
@ -56,7 +55,6 @@ const EncryptedSocketChat: React.FC<Props> = ({
}: Props): React.JSX.Element => {
const { t } = useTranslation();
const theme = useTheme();
const { origin, hostUrl, settings } = useContext<UseAppStoreType>(AppContext);
const { garage, slotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
@ -115,13 +113,11 @@ const EncryptedSocketChat: React.FC<Props> = ({
if (!slot?.token) return;
const { url, basePath } = federation
.getCoordinator(order.shortAlias)
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
const url = federation.getCoordinator(order.shortAlias).url;
websocketClient
.open(
`${url.replace(/^https?:\/\//, 'ws://') + basePath}/ws/chat/${
`${url.replace(/^https?:\/\//, 'ws://')}/ws/chat/${
order.id
}/?token_sha256_hex=${sha256(slot?.token)}`,
)

View File

@ -13,7 +13,6 @@ import ChatHeader from '../ChatHeader';
import { type EncryptedChatMessage, type ServerMessage } from '..';
import { apiClient } from '../../../../services/api';
import ChatBottom from '../ChatBottom';
import { type UseAppStoreType, AppContext } from '../../../../contexts/AppContext';
import {
type UseFederationStoreType,
FederationContext,
@ -55,7 +54,6 @@ const EncryptedTurtleChat: React.FC<Props> = ({
}: Props): React.JSX.Element => {
const { t } = useTranslation();
const theme = useTheme();
const { origin, hostUrl, settings } = useContext<UseAppStoreType>(AppContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
@ -95,11 +93,9 @@ const EncryptedTurtleChat: React.FC<Props> = ({
if (!shortAlias) return;
const { url, basePath } = federation
.getCoordinator(shortAlias)
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
const url = federation.getCoordinator(shortAlias).url;
apiClient
.get(url + basePath, `/api/chat/?order_id=${order.id}&offset=${lastIndex}`, {
.get(url, `/api/chat/?order_id=${order.id}&offset=${lastIndex}`, {
tokenSHA256: garage.getSlot()?.getRobot()?.tokenSHA256 ?? '',
})
.then((results: object) => {
@ -197,13 +193,11 @@ const EncryptedTurtleChat: React.FC<Props> = ({
}
// If input string contains '#' send unencrypted and unlogged message
else if (value.substring(0, 1) === '#') {
const { url, basePath } = federation
.getCoordinator(garage.getSlot()?.activeOrder?.shortAlias ?? '')
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
const url = federation.getCoordinator(garage.getSlot()?.activeOrder?.shortAlias ?? '').url;
onSendMessage(value);
apiClient
.post(
url + basePath,
url,
`/api/chat/`,
{
PGP_message: value,
@ -232,12 +226,12 @@ const EncryptedTurtleChat: React.FC<Props> = ({
onSendMessage(value);
encryptMessage(value, robot?.pubKey, peerPubKey ?? '', robot?.encPrivKey, slot?.token)
.then((encryptedMessage) => {
const { url, basePath } = federation
.getCoordinator(garage.getSlot()?.activeOrder?.shortAlias ?? '')
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
const url = federation.getCoordinator(
garage.getSlot()?.activeOrder?.shortAlias ?? '',
).url;
apiClient
.post(
url + basePath,
url,
`/api/chat/`,
{
PGP_message: String(encryptedMessage).split('\n').join('\\'),

View File

@ -49,14 +49,8 @@ const EncryptedChat: React.FC<Props> = ({
useEffect(() => {
// const slot = garage.getSlot();
const coordinator = federation.getCoordinator(order.shortAlias);
federation.roboPool.connect([
coordinator.getRelayUrl(settings.network, hostUrl, settings.selfhostedClient),
]);
// const since = new Date(order.created_at);
// since.setDate(since.getDate() - 2);
// federation.roboPool.subscribeChat(
// [order.maker_nostr_pubkey, order.taker_nostr_pubkey],
// Math.floor((since.getTime() / 1000)),

View File

@ -164,7 +164,7 @@ export interface UseAppStoreType {
export const initialAppContext: UseAppStoreType = {
theme: undefined,
torStatus: 'STARTING',
settings: new Settings(),
settings: getSettings(),
setSettings: () => {},
page: entryPage,
setPage: () => {},
@ -202,7 +202,7 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): React
const origin = initialAppContext.origin;
const [client, view] = window.RobosatsSettings.split('-');
const [settings, setSettings] = useState<Settings>(getSettings());
const [settings, setSettings] = useState<Settings>(initialAppContext.settings);
const [theme, setTheme] = useState<Theme>(() => {
return makeTheme(settings);
});

View File

@ -23,8 +23,10 @@ export interface UseFederationStoreType {
addNewCoordinator: (alias: string, url: string) => void;
}
const initialFederation = new Federation('onion', new Settings(), '');
export const initialFederationContext: UseFederationStoreType = {
federation: new Federation('onion', new Settings(), ''),
federation: initialFederation,
coordinatorUpdatedAt: '',
federationUpdatedAt: '',
addNewCoordinator: () => {},
@ -38,7 +40,7 @@ export const FederationContextProvider = ({
const { settings, page, origin, hostUrl, open, torStatus, client, fav } =
useContext<UseAppStoreType>(AppContext);
const { setMaker, garage } = useContext<UseGarageStoreType>(GarageContext);
const [federation] = useState(new Federation(origin, settings, hostUrl));
const [federation] = useState(initialFederationContext.federation);
const [coordinatorUpdatedAt, setCoordinatorUpdatedAt] = useState<string>(
new Date().toISOString(),
);
@ -55,9 +57,7 @@ export const FederationContextProvider = ({
useEffect(() => {
if (client !== 'mobile' || torStatus === 'ON' || !settings.useProxy) {
void federation.updateUrl(origin, settings, hostUrl);
void federation.loadLimits();
federation.setConnection(settings, fav.coordinator);
federation.setConnection(origin, settings, hostUrl, fav.coordinator);
}
}, [settings.network, settings.useProxy, torStatus, settings.connection]);

View File

@ -141,7 +141,6 @@ export class Coordinator {
this.testnetNodesPubkeys = value.testnetNodesPubkeys;
this.nostrHexPubkey = value.nostrHexPubkey;
this.url = '';
this.basePath = '';
this.updateUrl(origin, settings, hostUrl);
}
@ -164,7 +163,6 @@ export class Coordinator {
public mainnetNodesPubkeys: string[] | undefined;
public testnetNodesPubkeys: string[] | undefined;
public url: string;
public basePath: string;
public nostrHexPubkey: string;
// These properties are fetched from coordinator API
@ -177,11 +175,9 @@ export class Coordinator {
updateUrl = (origin: Origin, settings: Settings, hostUrl: string): void => {
if (settings.selfhostedClient && this.shortAlias !== 'local') {
this.url = hostUrl;
this.basePath = `/${settings.network}/${this.shortAlias}`;
this.url = `${hostUrl}/${settings.network}/${this.shortAlias}`;
} else {
this.url = String(this[settings.network]?.[origin]);
this.basePath = '';
}
};
@ -200,7 +196,7 @@ export class Coordinator {
this.book = {};
apiClient
.get(this.url, `${this.basePath}/api/book/`)
.get(this.url, `/api/book/`)
.then((data) => {
if (!data?.not_found) {
this.book = (data as PublicOrder[]).reduce<Record<string, PublicOrder>>((book, order) => {
@ -229,7 +225,7 @@ export class Coordinator {
this.loadingLimits = true;
apiClient
.get(this.url, `${this.basePath}/api/limits/`)
.get(this.url, `/api/limits/`)
.then((data) => {
if (data !== null) {
const newLimits = data as LimitList;
@ -258,7 +254,7 @@ export class Coordinator {
this.loadingInfo = true;
apiClient
.get(this.url, `${this.basePath}/api/info/`)
.get(this.url, `/api/info/`)
.then((data) => {
if (data !== null) {
this.info = data as Info;
@ -287,30 +283,9 @@ export class Coordinator {
this.book = {};
};
getBaseUrl = (): string => {
return this.url + this.basePath;
};
getEndpoint = (
network: 'mainnet' | 'testnet',
origin: Origin,
selfHosted: boolean,
hostUrl: string,
): { url: string; basePath: string } => {
if (selfHosted && this.shortAlias !== 'local') {
return { url: hostUrl, basePath: `/${network}/${this.shortAlias}` };
} else {
return { url: String(this[network][origin]), basePath: '' };
}
};
getRelayUrl = (network: 'mainnet' | 'testnet', hostUrl: string, selfHosted: boolean): string => {
const protocol = hostUrl.includes('https') ? 'wss' : 'ws';
if (selfHosted && this.shortAlias !== 'local') {
return `${protocol}://${hostUrl.replace(/^https?:\/\//, '')}/${network}/${this.shortAlias}/relay/`;
} else {
return `${protocol}://${this.url.replace(/^https?:\/\//, '')}/relay/`;
}
getRelayUrl = (hostUrl: string): string => {
const protocol = hostUrl.includes('https') ? 'wss://' : 'ws://';
return this.url.replace(/^https?:\/\//, protocol) + '/relay/';
};
}

View File

@ -62,10 +62,10 @@ export class Federation {
const tesnetHost = Object.values(this.coordinators).find((coor) => {
return Object.values(coor.testnet).includes(url);
});
if (tesnetHost) settings.network = 'testnet';
this.network = settings.network;
if (tesnetHost) this.network = 'testnet';
this.connection = null;
this.roboPool = new RoboPool(settings, hostUrl, Object.values(this.coordinators));
this.roboPool = new RoboPool(settings);
}
private coordinators: Record<string, Coordinator>;
@ -73,23 +73,38 @@ export class Federation {
public book: Record<string, PublicOrder | undefined>;
public loading: boolean;
public connection: 'api' | 'nostr' | null;
public network: 'testnet' | 'mainnet';
public hooks: Record<FederationHooks, Array<() => void>>;
public roboPool: RoboPool;
setConnection = (settings: Settings, coordinator: string): void => {
setConnection = (
origin: Origin,
settings: Settings,
hostUrl: string,
coordinator: string,
): void => {
this.connection = settings.connection;
this.loading = true;
this.book = {};
this.exchange.loadingCache = this.roboPool.relays.length;
this.network = settings.network;
const coordinators = Object.values(this.coordinators);
coordinators.forEach((c) => c.updateUrl(origin, settings, hostUrl));
this.roboPool.updateRelays(hostUrl, Object.values(this.coordinators));
void this.loadLimits();
if (this.connection === 'nostr') {
this.roboPool.connect();
this.loadBookNostr(coordinator !== 'any');
} else {
this.roboPool.close();
void this.loadBook();
}
const federationUrls = Object.values(this.coordinators).map((c) => c.url);
systemClient.setCookie('federation', JSON.stringify(federationUrls));
};
refreshBookHosts: (robosatsOnly: boolean) => void = (robosatsOnly) => {
@ -101,8 +116,10 @@ export class Federation {
loadBookNostr = (robosatsOnly: boolean): void => {
this.roboPool.subscribeBook(robosatsOnly, {
onevent: (event) => {
const { dTag, publicOrder } = eventToPublicOrder(event);
if (publicOrder) {
const { dTag, publicOrder, network } = eventToPublicOrder(event);
console.log(network);
console.log(this.network);
if (publicOrder && network == this.network) {
this.book[dTag] = publicOrder;
} else {
this.book[dTag] = undefined;
@ -160,20 +177,6 @@ export class Federation {
this.triggerHook('onFederationUpdate');
};
updateUrl = async (origin: Origin, settings: Settings, hostUrl: string): Promise<void> => {
const federationUrls = {};
for (const coor of Object.values(this.coordinators)) {
const { url, basePath } = coor.getEndpoint(
settings.network,
origin,
settings.selfhostedClient,
hostUrl,
);
federationUrls[coor.shortAlias] = url + basePath;
}
systemClient.setCookie('federation', JSON.stringify(federationUrls));
};
loadInfo = async (): Promise<void> => {
this.exchange.info = {
num_public_buy_orders: 0,

View File

@ -203,11 +203,10 @@ class Order {
if (slot) {
const coordinator = federation.getCoordinator(this.shortAlias);
const { basePath, url } = coordinator;
const authHeaders = slot.getRobot()?.getAuthHeaders();
if (!authHeaders) return this;
const data = await apiClient
.post(url + basePath, '/api/make/', body, authHeaders)
.post(coordinator.url, '/api/make/', body, authHeaders)
.catch((e) => {
console.log(e);
});
@ -234,9 +233,8 @@ class Order {
if (slot) {
const coordinator = federation.getCoordinator(this.shortAlias);
const { basePath, url } = coordinator;
const data = await apiClient
.post(url + basePath, `/api/order/?order_id=${Number(this.id)}`, action, {
.post(coordinator.url, `/api/order/?order_id=${Number(this.id)}`, action, {
tokenSHA256: slot?.getRobot()?.tokenSHA256 ?? '',
})
.catch((e) => {
@ -254,9 +252,8 @@ class Order {
const coordinator = federation.getCoordinator(this.shortAlias);
const authHeaders = slot.getRobot()?.getAuthHeaders();
if (!authHeaders) return this;
const { basePath, url } = coordinator;
const data = await apiClient
.get(url + basePath, `/api/order/?order_id=${this.id}`, authHeaders)
.get(coordinator.url, `/api/order/?order_id=${this.id}`, authHeaders)
.catch((e) => {
console.log(e);
});

View File

@ -55,7 +55,7 @@ class Robot {
this.loading = true;
await apiClient
.get(coordinator.url, `${coordinator.basePath}/api/robot/`, authHeaders)
.get(coordinator.url, '/api/robot/', authHeaders)
.then((data: object) => {
if (data?.bad_request) {
console.error(data?.bad_request);
@ -99,7 +99,7 @@ class Robot {
const data = await apiClient
.post(
coordinator.url,
`${coordinator.basePath}/api/reward/`,
'/api/reward/',
{
invoice: signedInvoice,
},
@ -118,12 +118,7 @@ class Robot {
const coordinator = federation.getCoordinator(this.shortAlias);
await apiClient
.post(
coordinator.url,
`${coordinator.basePath}/api/stealth/`,
{ wantsStealth },
{ tokenSHA256: this.tokenSHA256 },
)
.post(coordinator.url, '/api/stealth/', { wantsStealth }, { tokenSHA256: this.tokenSHA256 })
.catch((e) => {
console.log(e);
});
@ -143,7 +138,7 @@ class Robot {
};
apiClient
.post(coordinator.url, `${coordinator.basePath}/api/review/`, body, {
.post(coordinator.url, '/api/review/', body, {
tokenSHA256: this.tokenSHA256,
})
.then((data) => {

View File

@ -10,21 +10,24 @@ interface RoboPoolEvents {
}
class RoboPool {
constructor(settings: Settings, hostUrl: string, coordinators: Coordinator[]) {
constructor(settings: Settings) {
this.network = settings.network ?? 'mainnet';
this.relays = [];
const federationRelays = coordinators.map((coord) =>
coord.getRelayUrl(settings.network, hostUrl, settings.selfhostedClient),
);
}
if (settings.host) {
const protocol = hostUrl.includes('https') ? 'wss' : 'ws';
const hostNostr = `${protocol}://${settings.host.replace(/^https?:\/\//, '')}/relay/`;
if (federationRelays.includes(hostNostr)) {
this.relays.push(hostNostr);
}
}
public relays: string[];
public network: string;
public webSockets: Record<string, WebsocketConnection | null> = {};
private readonly messageHandlers: Array<(url: string, event: MessageEvent) => void> = [];
updateRelays = (hostUrl: string, coordinators: Coordinator[]) => {
this.close();
this.relays = [];
const federationRelays = coordinators.map((coord) => coord.getRelayUrl(hostUrl));
const hostRelay = federationRelays.find((relay) => relay.includes(hostUrl));
if (hostRelay) this.relays.push(hostRelay);
while (this.relays.length < 3) {
const randomRelay =
@ -33,13 +36,8 @@ class RoboPool {
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> = [];
this.connect();
};
connect = (relays: string[] = this.relays): void => {
relays.forEach((url: string) => {

View File

@ -7,7 +7,9 @@ import thirdParties from '../../static/thirdparties.json';
import currencyDict from '../../static/assets/currencies.json';
import defaultFederation from '../../static/federation.json';
const eventToPublicOrder = (event: Event): { dTag: string; publicOrder: PublicOrder | null } => {
const eventToPublicOrder = (
event: Event,
): { dTag: string; publicOrder: PublicOrder | null; network: string } => {
const publicOrder: PublicOrder = {
id: 0,
coordinatorShortAlias: '',
@ -36,10 +38,12 @@ const eventToPublicOrder = (event: Event): { dTag: string; publicOrder: PublicOr
const statusTag = event.tags.find((t) => t[0] === 's') ?? [];
const dTag = event.tags.find((t) => t[0] === 'd') ?? [];
const network = event.tags.find((t) => t[0] === 'network') ?? [];
const coordinator = [...Object.values(defaultFederation), ...Object.values(thirdParties)].find(
(coord) => coord.nostrHexPubkey === event.pubkey,
);
if (!coordinator || statusTag[1] !== 'pending') return { dTag: dTag[1], publicOrder: null };
if (!coordinator || statusTag[1] !== 'pending')
return { dTag: dTag[1], publicOrder: null, network: network[1] };
publicOrder.coordinatorShortAlias = coordinator?.shortAlias;
publicOrder.federated = coordinator?.federated ?? false;
@ -104,7 +108,7 @@ const eventToPublicOrder = (event: Event): { dTag: string; publicOrder: PublicOr
if (!publicOrder.maker_hash_id)
publicOrder.maker_hash_id = `${publicOrder.id}${coordinator?.shortAlias}`;
return { dTag: dTag[1], publicOrder };
return { dTag: dTag[1], publicOrder, network: network[1] };
};
export const verifyCoordinatorToken: (event: Event) => boolean = (event) => {