Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- changed: Increase NYM mixFetch timeout to 120s for improved XMR makeSpend reliability.
- changed: Remove NYM mixFetch per-host request queue now that `@nymproject/mix-fetch` handles concurrency natively.

## 2.43.1 (2026-02-23)

- changed: Upgrade `@nymproject/mix-fetch` to improve performance with new concurrency changes.
Expand Down
18 changes: 11 additions & 7 deletions src/io/browser/browser-io.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { mixFetch } from '@nymproject/mix-fetch'
import { makeLocalStorageDisklet } from 'disklet'

import { LogBackend, makeLog } from '../../core/log/log'
import { EdgeFetchOptions, EdgeFetchResponse, EdgeIo } from '../../types/types'
import { scrypt } from '../../util/crypto/scrypt'
import { initMixFetch, queueMixFetch } from '../../util/nym'
import { initMixFetch, mixFetchOptions } from '../../util/nym'
import { fetchCorsProxy } from './fetch-cors-proxy'

// Only try CORS proxy/bridge techniques up to 5 times
Expand Down Expand Up @@ -50,12 +51,15 @@ export function makeBrowserIo(logBackend: LogBackend): EdgeIo {

if (privacy === 'nym') {
// Ensure mixFetch is initialized before use
await initMixFetch(log)
// Use queued fetch to handle mixFetch's one-request-per-host limitation
return await queueMixFetch(uri, {
...opts,
mode: 'unsafe-ignore-cors' as RequestMode
})
const nymMixFetch = await initMixFetch(log)
return await nymMixFetch(
uri,
{
...opts,
mode: 'unsafe-ignore-cors' as RequestMode
},
mixFetchOptions
)
}
if (corsBypass === 'always') {
return await fetchCorsProxy(uri, opts)
Expand Down
18 changes: 11 additions & 7 deletions src/io/react-native/react-native-worker.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { mixFetch } from '@nymproject/mix-fetch'
import hashjs from 'hash.js'
import HmacDRBG from 'hmac-drbg'
import { base64 } from 'rfc4648'
Expand All @@ -17,7 +18,7 @@ import {
EdgeFetchResponse,
EdgeIo
} from '../../types/types'
import { initMixFetch, queueMixFetch } from '../../util/nym'
import { initMixFetch, mixFetchOptions } from '../../util/nym'
import { hideProperties } from '../hidden-properties'
import { makeNativeBridge } from './native-bridge'
import { WorkerApi, YAOB_THROTTLE_MS } from './react-native-types'
Expand Down Expand Up @@ -177,12 +178,15 @@ async function makeIo(logBackend: LogBackend): Promise<EdgeIo> {

if (privacy === 'nym') {
// Ensure mixFetch is initialized before use
await initMixFetch(log)
// Use queued fetch to handle mixFetch's one-request-per-host limitation
const response = await queueMixFetch(uri, {
...opts,
mode: 'unsafe-ignore-cors' as RequestMode
})
const nymMixFetch = await initMixFetch(log)
const response = await nymMixFetch(
uri,
{
...opts,
mode: 'unsafe-ignore-cors' as RequestMode
},
mixFetchOptions
)
return response
}
if (corsBypass === 'always') {
Expand Down
54 changes: 4 additions & 50 deletions src/util/nym.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
createMixFetch,
IMixFetch,
mixFetch,
SetupMixFetchOps
} from '@nymproject/mix-fetch'

Expand All @@ -11,30 +10,15 @@ import { EdgeLog } from '../types/types'
* Configuration options for the NYM mixFetch client.
*/
export const mixFetchOptions: SetupMixFetchOps = {
forceTls: true // force WSS
forceTls: true, // force WSS
mixFetchOverride: {
requestTimeoutMs: 120000
}
}

// MixFetch initialization state
let mixFetchInitPromise: Promise<IMixFetch> | null = null

// Per-host request queue to handle mixFetch's one-request-per-host limitation
// Maps host -> Promise that resolves when current request completes (for chaining)
const hostRequestChains = new Map<string, Promise<Response>>()

/**
* Extract the host:port from a URI for queue keying
*/
function getHostKey(uri: string): string {
try {
const url = new URL(uri)
const port =
url.port !== '' ? url.port : url.protocol === 'https:' ? '443' : '80'
return `${url.hostname}:${port}`
} catch {
return uri
}
}

/**
* Initialize the NYM mixFetch client. Must be called before using mixFetch.
* Safe to call multiple times - subsequent calls return the same promise.
Expand All @@ -56,33 +40,3 @@ export async function initMixFetch(log: EdgeLog): Promise<IMixFetch> {
}
return await mixFetchInitPromise
}

/**
* Queue-wrapped mixFetch that serializes requests per host.
* mixFetch only allows one concurrent request per host, so we chain them.
*/
export async function queueMixFetch(
uri: string,
opts: RequestInit & { mode?: string }
): Promise<Response> {
const hostKey = getHostKey(uri)

// Get the current chain for this host (or resolved promise if none)
const previousChain = hostRequestChains.get(hostKey) ?? Promise.resolve()

// Chain our request after the previous one
const ourWork = previousChain
.catch(() => {}) // Ignore errors from previous request
.then(async () => await mixFetch(uri, opts, mixFetchOptions))
.finally(() => {
// Clean up if we're still the chain tail
if (hostRequestChains.get(hostKey) === ourWork) {
hostRequestChains.delete(hostKey)
}
})

// Store our chain BEFORE awaiting - ensures subsequent requests wait for us
hostRequestChains.set(hostKey, ourWork)

return await ourWork
}