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
- Check connectivity and retry — shim.fetchWithRetry already wraps the call, so a persistent failure means a hard network or server problem.
- If the endpoint uses self-signed TLS, pass options.ignoreTlsErrors = true (dev only).
- Verify localFilePath is under a writable RNFetchBlob.fs.dirs directory and the device has free space.
- Reproduce with shim.debugFetch(url) (defined in index.ts) to get the raw XHR response for diagnosis.
- 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
- Validate the URL with new URL() before downloading.
- Ensure the target directory is writable and disk has space.
- Use shim.debugFetch to diagnose opaque RNFetchBlob errors.
- Pass ignoreTlsErrors only in dev for self-signed endpoints.
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
- Could not download from ${modelUrl}: Error ${response.status
- uploadBlob: ${method} ${url}: ${error.toString()}
- Not a valid URL: ${url}
- Could not check for updates. The server rate limit has been
- Could not check for updates. Please try again later (Error $
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/9fdeb3ec38e7a23a.
Report an issue: GitHub.