diff --git a/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx b/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx index b5c4ea1c..f7d3787b 100644 --- a/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx +++ b/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx @@ -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): React.JSX.Element => { const { t } = useTranslation(); const theme = useTheme(); - const { origin, hostUrl, settings } = useContext(AppContext); const { garage, slotUpdatedAt } = useContext(GarageContext); const { federation } = useContext(FederationContext); @@ -115,13 +113,11 @@ const EncryptedSocketChat: React.FC = ({ 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)}`, ) diff --git a/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx b/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx index 3ad147fc..1121f47b 100644 --- a/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx +++ b/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx @@ -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): React.JSX.Element => { const { t } = useTranslation(); const theme = useTheme(); - const { origin, hostUrl, settings } = useContext(AppContext); const { federation } = useContext(FederationContext); const { garage } = useContext(GarageContext); @@ -95,11 +93,9 @@ const EncryptedTurtleChat: React.FC = ({ 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 = ({ } // 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 = ({ 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('\\'), diff --git a/frontend/src/components/TradeBox/EncryptedChat/index.tsx b/frontend/src/components/TradeBox/EncryptedChat/index.tsx index 9e2669dd..2c07020b 100644 --- a/frontend/src/components/TradeBox/EncryptedChat/index.tsx +++ b/frontend/src/components/TradeBox/EncryptedChat/index.tsx @@ -49,14 +49,8 @@ const EncryptedChat: React.FC = ({ 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)), diff --git a/frontend/src/contexts/AppContext.tsx b/frontend/src/contexts/AppContext.tsx index 7d7b1e9d..27394e0f 100644 --- a/frontend/src/contexts/AppContext.tsx +++ b/frontend/src/contexts/AppContext.tsx @@ -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(getSettings()); + const [settings, setSettings] = useState(initialAppContext.settings); const [theme, setTheme] = useState(() => { return makeTheme(settings); }); diff --git a/frontend/src/contexts/FederationContext.tsx b/frontend/src/contexts/FederationContext.tsx index f0a705cb..e96b6afc 100644 --- a/frontend/src/contexts/FederationContext.tsx +++ b/frontend/src/contexts/FederationContext.tsx @@ -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(AppContext); const { setMaker, garage } = useContext(GarageContext); - const [federation] = useState(new Federation(origin, settings, hostUrl)); + const [federation] = useState(initialFederationContext.federation); const [coordinatorUpdatedAt, setCoordinatorUpdatedAt] = useState( 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]); diff --git a/frontend/src/models/Coordinator.model.ts b/frontend/src/models/Coordinator.model.ts index 0b44ba68..59c09d85 100644 --- a/frontend/src/models/Coordinator.model.ts +++ b/frontend/src/models/Coordinator.model.ts @@ -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>((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/'; }; } diff --git a/frontend/src/models/Federation.model.ts b/frontend/src/models/Federation.model.ts index 23cc1762..db8efaa0 100644 --- a/frontend/src/models/Federation.model.ts +++ b/frontend/src/models/Federation.model.ts @@ -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; @@ -73,23 +73,38 @@ export class Federation { public book: Record; public loading: boolean; public connection: 'api' | 'nostr' | null; + public network: 'testnet' | 'mainnet'; public hooks: Record 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 => { - 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 => { this.exchange.info = { num_public_buy_orders: 0, diff --git a/frontend/src/models/Order.model.ts b/frontend/src/models/Order.model.ts index f5e20431..bceddfa9 100644 --- a/frontend/src/models/Order.model.ts +++ b/frontend/src/models/Order.model.ts @@ -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); }); diff --git a/frontend/src/models/Robot.model.ts b/frontend/src/models/Robot.model.ts index f61a9a1c..894e7cdf 100644 --- a/frontend/src/models/Robot.model.ts +++ b/frontend/src/models/Robot.model.ts @@ -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) => { diff --git a/frontend/src/services/RoboPool/index.ts b/frontend/src/services/RoboPool/index.ts index 0071f6cf..bc3d1f0a 100644 --- a/frontend/src/services/RoboPool/index.ts +++ b/frontend/src/services/RoboPool/index.ts @@ -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 = {}; + 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 = {}; - private readonly messageHandlers: Array<(url: string, event: MessageEvent) => void> = []; + this.connect(); + }; connect = (relays: string[] = this.relays): void => { relays.forEach((url: string) => { diff --git a/frontend/src/utils/nostr.ts b/frontend/src/utils/nostr.ts index de746943..5e92ffb1 100644 --- a/frontend/src/utils/nostr.ts +++ b/frontend/src/utils/nostr.ts @@ -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) => {