fix(app): allow removing pending attachment uploads (#55836)

This commit is contained in:
Kit Langton
2026-10-17 22:23:20 -07:00
committed by GitHub
Unverified
parent 0489de252a
commit dd3266a14b
3 changed files with 26 additions and 4 deletions
@@ -42,6 +42,7 @@ export function useAttachmentDestination(controls: Accessor<ComposerControls>) {
// from disk; nothing is buffered in the renderer.
function write(url: URL, file: File, password: string | undefined, report: (loaded: number) => void, signal: AbortSignal) {
return new Promise<string>((resolve, reject) => {
if (signal.aborted) return reject(new DOMException("Upload aborted", "AbortError"))
const xhr = new XMLHttpRequest()
xhr.open("POST", url)
xhr.responseType = "json"
@@ -0,0 +1,14 @@
import { expect, test } from "bun:test"
import { uploads } from "./uploads"
test("cancelling an upload drops it even when its work never settles", async () => {
const pending = uploads.track({ id: "stalled", filename: "image.png", mime: "image/png", size: 1024 }, () =>
new Promise<string>(() => {}),
)
expect(uploads.items().map((item) => item.id)).toEqual(["stalled"])
uploads.items()[0]?.cancel()
expect(await pending).toBeUndefined()
expect(uploads.items()).toEqual([])
})
@@ -25,13 +25,20 @@ export const uploads = {
work: (report: (loaded: number) => void, signal: AbortSignal) => Promise<T>,
): Promise<T | undefined> {
const controller = new AbortController()
// The work may wait where it cannot observe the signal, such as a request queued behind
// others, so cancelling settles without it.
const cancelled = new Promise<undefined>((resolve) =>
controller.signal.addEventListener("abort", () => resolve(undefined), { once: true }),
)
setState("items", (items) => [...items, { ...input, loaded: 0, cancel: () => controller.abort() }])
try {
return await work(
(loaded) => setState("items", (item) => item.id === input.id, "loaded", loaded),
controller.signal,
)
return await Promise.race([
work((loaded) => setState("items", (item) => item.id === input.id, "loaded", loaded), controller.signal),
cancelled,
])
} catch (error) {
if (controller.signal.aborted) return undefined
throw error