{"record":{"id":"ecc1963d09601106","repo":"schollz/croc","slug":"aborterror-ecc196","errorCode":"AbortError","errorMessage":"Transfer cancelled","messagePattern":"Transfer cancelled","errorType":"exception","errorClass":"DOMException","httpStatus":null,"severity":"info","filePath":"web/src/protocol/stored.ts","lineNumber":96,"sourceCode":"  totalSize: number;\n};\n\ntype CreatedStoredUpload = {\n  share: StoredShare;\n  uploadToken: string;\n};\n\nclass StoredHTTPError extends Error {\n  constructor(\n    message: string,\n    readonly status: number,\n  ) {\n    super(message);\n  }\n}\n\nfunction checkAbort(signal?: AbortSignal) {\n  if (signal?.aborted) throw new DOMException(\"Transfer cancelled\", \"AbortError\");\n}\n\nfunction base64URL(bytes: Uint8Array) {\n  let binary = \"\";\n  for (let offset = 0; offset < bytes.byteLength; offset += 0x8000) {\n    binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));\n  }\n  return btoa(binary).replaceAll(\"+\", \"-\").replaceAll(\"/\", \"_\").replace(/=+$/, \"\");\n}\n\nfunction fromBase64URL(value: string) {\n  const normalized = value.replaceAll(\"-\", \"+\").replaceAll(\"_\", \"/\");\n  const padded = normalized + \"=\".repeat((4 - (normalized.length % 4)) % 4);\n  const binary = atob(padded);\n  const bytes = new Uint8Array(binary.length);\n  for (let index = 0; index < binary.length; index += 1) {\n    bytes[index] = binary.charCodeAt(index);\n  }","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/stored.ts#L78-L114","documentation":"checkAbort() throws this DOMException (name AbortError) whenever the supplied AbortSignal is already aborted. It is sprinkled at loop boundaries of prepareStoredFiles hashing, chunk upload, chunk download, and putCiphertext retries so cancellation takes effect promptly and is uniformly represented as a standard AbortError.","triggerScenarios":"Passing an aborted signal to prepareStoredFiles, uploadStoredFiles, receiveStoredTransfer, or any putCiphertext/authorizedFetch call; or the signal aborting mid-loop and the next checkAbort firing before fetch's own signal handling would.","commonSituations":"User clicks a Cancel button wired to an AbortController; a React component unmounts and aborts its controller but an await chain still runs; UI code that reuses one controller for multiple sequential operations after aborting it earlier.","solutions":["Treat DOMException name === \"AbortError\" as a clean cancel: stop the flow, call sink.abort()/revoke if needed, and show a 'cancelled' state","Create a fresh AbortController for each new operation instead of reusing an aborted one","If the abort was unexpected, find who owns the controller and why it fired (component unmount, timeout, navigation)"],"exampleFix":"// before\nconst result = await uploadStoredFiles({ ..., signal: controller.signal }); // reuses aborted controller\n\n// after\ncontroller = new AbortController(); // fresh controller per operation\nconst result = await uploadStoredFiles({ ..., signal: controller.signal });","handlingStrategy":"try-catch","validationCode":"const assertNotAborted = (signal?: AbortSignal) => { if (signal?.aborted) throw new DOMException(\"Transfer cancelled\", \"AbortError\"); };","typeGuard":"const isAbort = (e: unknown): boolean => e instanceof DOMException && e.name === \"AbortError\";","tryCatchPattern":"try { await uploadStoredFiles({ files, settings, signal }); } catch (e) { if (e instanceof DOMException && e.name === \"AbortError\") { setStatus(\"Cancelled\"); return; } throw e; }","preventionTips":["Create a new AbortController per operation; never reuse an aborted one","Abort controllers on user action or unmount, and always handle the resulting AbortError explicitly"],"tags":["abort","cancel","user-cancel","async"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}