laurent22/joplin · error · Error

fetchBlob: ${method} ${url}: ${error.toString()}

Error message

fetchBlob: ${method} ${url}: ${error.toString()}

What it means

Wraps any exception thrown by the RNFetchBlob-backed mobile download path (shim.fetchBlob). The catch on line 129 re-throws a generic Error whose message is 'fetchBlob: <method> <url>: <original error>'. The original structured error is flattened via error.toString(), so the caller receives only a string and loses the RNFetchBlob error object and status code.

Source

Thrown at packages/app-mobile/utils/shim-init-react/index.ts:130

			// Returns an object that's roughly compatible with a standard Response object
			const output = {
				ok: response.respInfo.status < 400,
				path: response.data,
				status: response.respInfo.status,
				headers: response.respInfo.headers,
				// If response type is 'path' then calling text() or json() (or base64())
				// on RNFetchBlob response object will make it read the file on the native thread,
				// serialize it, and send over the RN bridge.
				// For larger files this can cause the app to crash.
				// For these type of responses we're not using the response text anyway
				// so can override it here to return empty values
				text: response.type === 'path' ? () => '' : response.text,
				json: response.type === 'path' ? () => {} : response.json,
			};

			return output;
		} catch (error) {
			throw new Error(`fetchBlob: ${method} ${url}: ${error.toString()}`);
		}
	};

	shim.uploadBlob = async function(url, options) {
		if (!options || !options.path) throw new Error('uploadBlob: source file path is missing');

		const headers = options.headers ? options.headers : {};
		const method = options.method ? options.method : 'POST';

		try {
			const response = await RNFetchBlob.config({
				trusty: options.ignoreTlsErrors,
			}).fetch(method, url, headers, RNFetchBlob.wrap(options.path));

			// Returns an object that's roughly compatible with a standard Response object
			return {
				ok: response.respInfo.status < 400,
				data: response.data,

View on GitHub (pinned to 2654b33620)

Solutions

  1. Check connectivity and retry — shim.fetchWithRetry already wraps the call, so a persistent failure means a hard network or server problem.
  2. If the endpoint uses self-signed TLS, pass options.ignoreTlsErrors = true (dev only).
  3. Verify localFilePath is under a writable RNFetchBlob.fs.dirs directory and the device has free space.
  4. Reproduce with shim.debugFetch(url) (defined in index.ts) to get the raw XHR response for diagnosis.
  5. Upgrade/reinstall rn-fetch-blob (or its maintained fork react-native-blob-util) to match the RN version.

Example fix

// before
await shim.fetchBlob(url, { path, method: 'GET' });
// after — add resilience and TLS override for known self-signed hosts
try {
	await shim.fetchBlob(url, { path, method: 'GET', ignoreTlsErrors: isDev });
} catch (e) {
	logger.error('Download failed', e.message);
	throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

if (!url || typeof url !== 'string') throw new Error('fetchBlob requires a URL string');
try { new URL(url); } catch { throw new Error(`Invalid URL: ${url}`); }
if (!(await shim.fsDriver().exists(dirname(localFilePath)))) await shim.fsDriver().mkdir(dirname(localFilePath));
await shim.fetchBlob(url, { path: localFilePath, method: options.method ?? 'GET' });

Type guard

function hasValidPath(options) { return !!options && typeof options.path === 'string' && options.path.length > 0; }

Try / catch

try {
  await shim.fetchBlob(url, { path, method: 'GET' });
} catch (e) {
  if (/Network|timeout|ETIMEDOUT/i.test(e.message)) {
    await backoffRetry(() => shim.fetchBlob(url, { path, method: 'GET' }));
  } else throw e;
}

Prevention

When it happens

Trigger: Network unreachable (airplane mode, no connectivity); invalid/unreachable URL; TLS handshake failure (self-signed cert, expired cert) when ignoreTlsErrors is false; RNFetchBlob native module misconfigured; the server returns an error that RNFetchBlob surfaces as a rejection; insufficient storage at localFilePath.

Common situations: Syncing on a flaky mobile network; downloading a resource attachment behind a self-signed HTTPS endpoint; RNFetchBlob/Expo upgrade broke the native fetch bridge; target path points to a non-writable directory; large file download interrupted.

Related errors


AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12). Data as JSON: /api/errors/9fdeb3ec38e7a23a. Report an issue: GitHub.