{"record":{"id":"7276e4637daf3c4c","repo":"laurent22/joplin","slug":"uploadblob-method-url-error-tostring","errorCode":null,"errorMessage":"uploadBlob: ${method} ${url}: ${error.toString()}","messagePattern":"uploadBlob: (.+?) (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/app-mobile/utils/shim-init-react/index.ts","lineNumber":155,"sourceCode":"\t\tconst headers = options.headers ? options.headers : {};\n\t\tconst method = options.method ? options.method : 'POST';\n\n\t\ttry {\n\t\t\tconst response = await RNFetchBlob.config({\n\t\t\t\ttrusty: options.ignoreTlsErrors,\n\t\t\t}).fetch(method, url, headers, RNFetchBlob.wrap(options.path));\n\n\t\t\t// Returns an object that's roughly compatible with a standard Response object\n\t\t\treturn {\n\t\t\t\tok: response.respInfo.status < 400,\n\t\t\t\tdata: response.data,\n\t\t\t\ttext: response.text,\n\t\t\t\tjson: response.json,\n\t\t\t\tstatus: response.respInfo.status,\n\t\t\t\theaders: response.respInfo.headers,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new Error(`uploadBlob: ${method} ${url}: ${error.toString()}`);\n\t\t}\n\t};\n\n\tshim.readLocalFileBase64 = async function(path) {\n\t\treturn RNFetchBlob.fs.readFile(path, 'base64');\n\t};\n\n\tshim.openUrl = url => {\n\t\treturn Linking.openURL(url);\n\t};\n\n\tshim.mobilePlatform = () => {\n\t\treturn Platform.OS as MobilePlatform;\n\t};\n\n\tshim.isAppleSilicon = () => {\n\t\treturn false;\n\t};","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/app-mobile/utils/shim-init-react/index.ts#L137-L173","documentation":"Wraps any exception from the RNFetchBlob-backed mobile upload path (shim.uploadBlob). The catch on line 154 flattens the original error into 'uploadBlob: <method> <url>: <original>'. Like fetchBlob, the structured RNFetchBlob error and HTTP status are lost in the re-thrown generic Error.","triggerScenarios":"Uploading a resource blob to the sync target while offline; options.path does not exist or is unreadable; the server rejects the multipart body (auth failure, 413 payload too large, wrong content-type); TLS failure on the upload endpoint; RNFetchBlob.wrap() fails on a non-native path.","commonSituations":"E2EE sync upload to Joplin Server/Nextcloud/OneDrive with an expired token; uploading a large attachment exceeding server body limit; the source file was moved/deleted after the path was computed; reverse proxy timing out on slow upstream.","solutions":["Confirm options.path exists and is a native filesystem path (not a file:// URI) before calling uploadBlob.","Check auth token validity and refresh if the server returns 401/403 (visible via shim.debugFetch on the same URL).","For large files, verify the sync target's max upload size and increase it or chunk the resource.","Pass ignoreTlsErrors only in dev for self-signed endpoints; for production install the correct root CA.","Upgrade rn-fetch-blob/react-native-blob-util to match the React Native version."],"exampleFix":"// before\nawait shim.uploadBlob(url, { path: resourcePath, method: 'POST' });\n// after — guard the source file and capture status\nif (!(await shim.fsDriver().exists(resourcePath))) throw new Error('Missing upload source');\nconst resp = await shim.uploadBlob(url, { path: resourcePath, method: 'POST', headers: authHeaders });\nif (!resp.ok) throw new Error(`Upload rejected: ${resp.status}`);","handlingStrategy":"retry","validationCode":"if (!(await shim.fsDriver().exists(options.path))) throw new Error('uploadBlob: source missing');\nif (!options.headers?.Authorization) throw new Error('uploadBlob: auth header missing');\nawait shim.uploadBlob(url, options);","typeGuard":"function isUploadOptions(o) { return !!o && typeof o.path === 'string' && (!!o.method || true); }","tryCatchPattern":"try {\n  const resp = await shim.uploadBlob(url, options);\n  if (!resp.ok) throw new Error(`upload ${resp.status}`);\n} catch (e) {\n  if (/Network|timeout/i.test(e.message)) await backoffRetry(() => shim.uploadBlob(url, options));\n  else throw e;\n}","preventionTips":["Verify the source file exists and is a native path before uploading.","Keep auth tokens fresh; surface 401s for re-auth.","Confirm the server's max upload size for large resources."],"tags":["mobile","network","rn-fetch-blob","upload","sync"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}