schollz/croc · info · DOMException

AbortError

AbortError

Error message

Transfer cancelled

What it means

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.

Source

Thrown at web/src/protocol/stored.ts:96

  totalSize: number;
};

type CreatedStoredUpload = {
  share: StoredShare;
  uploadToken: string;
};

class StoredHTTPError extends Error {
  constructor(
    message: string,
    readonly status: number,
  ) {
    super(message);
  }
}

function checkAbort(signal?: AbortSignal) {
  if (signal?.aborted) throw new DOMException("Transfer cancelled", "AbortError");
}

function base64URL(bytes: Uint8Array) {
  let binary = "";
  for (let offset = 0; offset < bytes.byteLength; offset += 0x8000) {
    binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
  }
  return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
}

function fromBase64URL(value: string) {
  const normalized = value.replaceAll("-", "+").replaceAll("_", "/");
  const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
  const binary = atob(padded);
  const bytes = new Uint8Array(binary.length);
  for (let index = 0; index < binary.length; index += 1) {
    bytes[index] = binary.charCodeAt(index);
  }

View on GitHub (pinned to e25f1bdc04)

Solutions

  1. Treat DOMException name === "AbortError" as a clean cancel: stop the flow, call sink.abort()/revoke if needed, and show a 'cancelled' state
  2. Create a fresh AbortController for each new operation instead of reusing an aborted one
  3. If the abort was unexpected, find who owns the controller and why it fired (component unmount, timeout, navigation)

Example fix

// before
const result = await uploadStoredFiles({ ..., signal: controller.signal }); // reuses aborted controller

// after
controller = new AbortController(); // fresh controller per operation
const result = await uploadStoredFiles({ ..., signal: controller.signal });
Defensive patterns

Strategy: try-catch

Validate before calling

const assertNotAborted = (signal?: AbortSignal) => { if (signal?.aborted) throw new DOMException("Transfer cancelled", "AbortError"); };

Type guard

const isAbort = (e: unknown): boolean => e instanceof DOMException && e.name === "AbortError";

Try / catch

try { await uploadStoredFiles({ files, settings, signal }); } catch (e) { if (e instanceof DOMException && e.name === "AbortError") { setStatus("Cancelled"); return; } throw e; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15). Data as JSON: /api/errors/ecc1963d09601106. Report an issue: GitHub.