From cb91393cbba636afb82f38f6429847164f1b73ec Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Mon, 2 Mar 2026 19:37:37 -0800 Subject: [PATCH 1/4] Remove NYM mixFetch per-host request queue The @nymproject/mix-fetch library now handles concurrency natively, so the custom per-host serialization queue is no longer needed. queueMixFetch is now a thin pass-through to mixFetch. Made-with: Cursor --- CHANGELOG.md | 2 ++ src/io/browser/browser-io.ts | 1 - src/io/react-native/react-native-worker.ts | 1 - src/util/nym.ts | 42 ++-------------------- 4 files changed, 4 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77a86e20..95d79510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- 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. diff --git a/src/io/browser/browser-io.ts b/src/io/browser/browser-io.ts index 80fa556e..1db9371e 100644 --- a/src/io/browser/browser-io.ts +++ b/src/io/browser/browser-io.ts @@ -51,7 +51,6 @@ 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 diff --git a/src/io/react-native/react-native-worker.ts b/src/io/react-native/react-native-worker.ts index a71b31a1..95ea6a9b 100644 --- a/src/io/react-native/react-native-worker.ts +++ b/src/io/react-native/react-native-worker.ts @@ -178,7 +178,6 @@ async function makeIo(logBackend: LogBackend): Promise { 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 diff --git a/src/util/nym.ts b/src/util/nym.ts index b1bee731..be6bdd3d 100644 --- a/src/util/nym.ts +++ b/src/util/nym.ts @@ -17,24 +17,6 @@ export const mixFetchOptions: SetupMixFetchOps = { // MixFetch initialization state let mixFetchInitPromise: Promise | 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>() - -/** - * 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. @@ -58,31 +40,11 @@ export async function initMixFetch(log: EdgeLog): Promise { } /** - * Queue-wrapped mixFetch that serializes requests per host. - * mixFetch only allows one concurrent request per host, so we chain them. + * Thin wrapper around mixFetch with pre-configured options. */ export async function queueMixFetch( uri: string, opts: RequestInit & { mode?: string } ): Promise { - 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 + return await mixFetch(uri, opts, mixFetchOptions) } From d8b74e0e10ea53041adbbdf73ff085de74b7dca3 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Mon, 2 Mar 2026 20:48:47 -0800 Subject: [PATCH 2/4] Inline NYM mixFetch calls in IO layers Remove the queueMixFetch wrapper from util/nym and call mixFetch inline at browser and react-native NYM call sites. This follows PR feedback and reduces unnecessary abstraction while keeping initMixFetch setup behavior unchanged. --- src/io/browser/browser-io.ts | 15 ++++++++++----- src/io/react-native/react-native-worker.ts | 15 ++++++++++----- src/util/nym.ts | 11 ----------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/io/browser/browser-io.ts b/src/io/browser/browser-io.ts index 1db9371e..b66fbf09 100644 --- a/src/io/browser/browser-io.ts +++ b/src/io/browser/browser-io.ts @@ -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 @@ -51,10 +52,14 @@ export function makeBrowserIo(logBackend: LogBackend): EdgeIo { if (privacy === 'nym') { // Ensure mixFetch is initialized before use await initMixFetch(log) - return await queueMixFetch(uri, { - ...opts, - mode: 'unsafe-ignore-cors' as RequestMode - }) + return await mixFetch( + uri, + { + ...opts, + mode: 'unsafe-ignore-cors' as RequestMode + }, + mixFetchOptions + ) } if (corsBypass === 'always') { return await fetchCorsProxy(uri, opts) diff --git a/src/io/react-native/react-native-worker.ts b/src/io/react-native/react-native-worker.ts index 95ea6a9b..f7cd2c54 100644 --- a/src/io/react-native/react-native-worker.ts +++ b/src/io/react-native/react-native-worker.ts @@ -1,3 +1,4 @@ +import { mixFetch } from '@nymproject/mix-fetch' import hashjs from 'hash.js' import HmacDRBG from 'hmac-drbg' import { base64 } from 'rfc4648' @@ -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' @@ -178,10 +179,14 @@ async function makeIo(logBackend: LogBackend): Promise { if (privacy === 'nym') { // Ensure mixFetch is initialized before use await initMixFetch(log) - const response = await queueMixFetch(uri, { - ...opts, - mode: 'unsafe-ignore-cors' as RequestMode - }) + const response = await mixFetch( + uri, + { + ...opts, + mode: 'unsafe-ignore-cors' as RequestMode + }, + mixFetchOptions + ) return response } if (corsBypass === 'always') { diff --git a/src/util/nym.ts b/src/util/nym.ts index be6bdd3d..e3b97f87 100644 --- a/src/util/nym.ts +++ b/src/util/nym.ts @@ -1,7 +1,6 @@ import { createMixFetch, IMixFetch, - mixFetch, SetupMixFetchOps } from '@nymproject/mix-fetch' @@ -38,13 +37,3 @@ export async function initMixFetch(log: EdgeLog): Promise { } return await mixFetchInitPromise } - -/** - * Thin wrapper around mixFetch with pre-configured options. - */ -export async function queueMixFetch( - uri: string, - opts: RequestInit & { mode?: string } -): Promise { - return await mixFetch(uri, opts, mixFetchOptions) -} From 10799e1e4c3b746803a154efddd5701b20ffcc0b Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Mon, 2 Mar 2026 21:33:53 -0800 Subject: [PATCH 3/4] Increase NYM mixFetch timeout to 120s Made-with: Cursor --- CHANGELOG.md | 1 + src/util/nym.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95d79510..1bb94985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 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) diff --git a/src/util/nym.ts b/src/util/nym.ts index e3b97f87..fc623823 100644 --- a/src/util/nym.ts +++ b/src/util/nym.ts @@ -10,7 +10,10 @@ 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 From 72c483372ff02b7fd65fa3b102a72223ba09603b Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Sun, 8 Mar 2026 15:18:37 -0700 Subject: [PATCH 4/4] Fix: Use initialized mix fetch instance on Android The initMixFetch() function returns a properly initialized IMixFetch instance, but the code was discarding it and calling the raw mixFetch() function instead. On Android, the raw function uses an uninitialized WASM context, causing a panic. Fix: Store and use the returned IMixFetch instance in both react-native-io and browser-io fetch handlers. Fixes the 'panic: JavaScript error: mix fetch hasn'\''t been initialised' error on Android while keeping it working on iOS. --- src/io/browser/browser-io.ts | 4 ++-- src/io/react-native/react-native-worker.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/io/browser/browser-io.ts b/src/io/browser/browser-io.ts index b66fbf09..de007625 100644 --- a/src/io/browser/browser-io.ts +++ b/src/io/browser/browser-io.ts @@ -51,8 +51,8 @@ export function makeBrowserIo(logBackend: LogBackend): EdgeIo { if (privacy === 'nym') { // Ensure mixFetch is initialized before use - await initMixFetch(log) - return await mixFetch( + const nymMixFetch = await initMixFetch(log) + return await nymMixFetch( uri, { ...opts, diff --git a/src/io/react-native/react-native-worker.ts b/src/io/react-native/react-native-worker.ts index f7cd2c54..f5712a3f 100644 --- a/src/io/react-native/react-native-worker.ts +++ b/src/io/react-native/react-native-worker.ts @@ -178,8 +178,8 @@ async function makeIo(logBackend: LogBackend): Promise { if (privacy === 'nym') { // Ensure mixFetch is initialized before use - await initMixFetch(log) - const response = await mixFetch( + const nymMixFetch = await initMixFetch(log) + const response = await nymMixFetch( uri, { ...opts,