{"record":{"id":"ebeaacf15546fa14","repo":"schollz/croc","slug":"transfer-refused","errorCode":null,"errorMessage":"Transfer refused","messagePattern":"Transfer refused","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"web/src/protocol/client.ts","lineNumber":741,"sourceCode":"      t: \"externalip\",\n      m: relay.externalIP,\n      b: peerPake.b,\n    }, key);\n    const peerIP = await receiveControl(control, key);\n    if (peerIP.t !== \"externalip\") throw new Error(\"Sender did not secure the channel\");\n\n    const fileInfo = await receiveControl(control, key);\n    if (fileInfo.t === \"error\") throw new Error(fileInfo.m || \"Sender cancelled\");\n    if (fileInfo.t !== \"fileinfo\" || !fileInfo.b) {\n      throw new Error(\"Sender did not provide file metadata\");\n    }\n    const sender = JSON.parse(textDecoder.decode(fileInfo.b)) as SenderInfoWire;\n    const offer = validateSenderInfo(sender);\n    callbacks.onStatus?.(\"Review the incoming files\");\n    const destination = await callbacks.onOffer(offer);\n    if (!destination) {\n      await sendControl(control, { t: \"error\", m: \"refusing files\" }, key);\n      throw new Error(\"Transfer refused\");\n    }\n\n    for (const folder of offer.emptyFolders) {\n      await destination.createEmptyFolder(folder);\n    }\n    let totalTransferred = 0;\n    receiver = new DataReceiver(data, key, offer.noCompress);\n    for (let fileIndex = 0; fileIndex < offer.files.length; fileIndex += 1) {\n      checkAbort(signal);\n      const file = offer.files[fileIndex];\n      if (file.size === 0) {\n        const sink = await destination.openFile(file);\n        try {\n          await sink.finalize();\n          callbacks.onStatus?.(`Verifying ${file.path}`);\n          await verifySink(sink, file.hash);\n          await sink.commit();\n          callbacks.onFileComplete?.(file.path);","sourceCodeStart":723,"sourceCodeEnd":759,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/client.ts#L723-L759","documentation":"Thrown by the recipient after callbacks.onOffer returned no destination, meaning the receiving application/user declined the offered files. The recipient sends an 'error' control message ('refusing files') to inform the sender and then throws to unwind the transfer. This is an intentional, user-driven abort rather than a fault, but it propagates as an Error to the receiveFiles caller.","triggerScenarios":"onOffer resolves to undefined/null/void because the user clicked Decline, no storage picker was granted, or application logic rejected the offer (unexpected sender, wrong file count, policy checks).","commonSituations":"User declines the transfer prompt; File System Access API picker dismissed without a selection; application-level policy (allow-list of peer machine IDs) refuses the sender; automated tests resolving onOffer with undefined.","solutions":["Treat this error as expected control flow: catch it and show a 'declined' state instead of a generic failure","If declining was unintentional, fix the onOffer implementation so it resolves only on explicit refusal and returns a destination otherwise","Ensure the storage-picker flow distinguishes 'user cancelled the picker' from 'user declined the transfer' if you want different UX","No protocol fix is needed — the sender is correctly notified with 'refusing files'"],"exampleFix":"// before\nconst destination = await callbacks.onOffer(offer);\nif (!destination) {\n  await sendControl(control, { t: \"error\", m: \"refusing files\" }, key);\n  throw new Error(\"Transfer refused\");\n}\n\n// caller: distinguish refusal from real failures\ntry {\n  await receiveFiles({ ... });\n} catch (e) {\n  if (e instanceof Error && e.message === \"Transfer refused\") {\n    showStatus(\"You declined the files\");\n    return;\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Make refusal explicit in your onOffer implementation\nconst destination = await showOfferDialog(offer); // resolves null only on explicit Decline\nif (!destination) { /* let receiveFiles throw Transfer refused and handle it */ }","typeGuard":"function isTransferRefused(e: unknown): boolean {\n  return e instanceof Error && e.message === \"Transfer refused\";\n}","tryCatchPattern":"try {\n  await receiveFiles(opts);\n} catch (e) {\n  if (e instanceof Error && e.message === \"Transfer refused\") {\n    showStatus(\"You declined the files\"); return; // expected, not an error state\n  }\n  throw e;\n}","preventionTips":["Design onOffer so it resolves a destination on accept and rejects/aborts on infrastructure failures — never silently return undefined","Map this exact message to a friendly 'declined' UI state instead of a generic error screen","Distinguish picker-dismissal from explicit decline in your UI if the distinction matters"],"tags":["ux","cancel","protocol","expected"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}