How to Resume an Interrupted File Transfer (Without Starting Over)
Chunked streaming, per-chunk acknowledgements, and the File System Access API — how modern transfer tools survive flaky networks.
Half-completed transfers used to be the single most demoralizing experience of the dial-up era. We told ourselves that broadband had fixed it. It hadn't. A 30 GB transfer over a flaky hotel Wi-Fi connection, a sudden mobile network handoff, or a laptop that picks the wrong moment to suspend can still cost you an hour and force you to start over. The fix isn't faster networks. The fix is making transfers stateful, chunked, and acknowledgeable.
Why "just retry" doesn't work
A naive transfer is one giant request. The sender opens a stream, pushes bytes until done. If the stream breaks anywhere — TCP connection dropped, browser tab refreshed, OS suspended — there's no state on either side that remembers how far it got. Even if you do remember the byte offset, the server might not, or the file might have been written into an opaque object store you can't seek into.
The HTTP spec has had a partial answer for decades: range requests. Downloading a file via HTTP from a server that supports ranges, you can ask for bytes 1,000,000 to 2,000,000 specifically, which makes it possible to resume downloads. But that only works because the server has the whole file ready to seek in.
The chunked-streaming pattern
Modern real-time file transfer tools (File Tunnel included) treat the file as a sequence of fixed-size chunks: 4 MB chunks are a common choice. The protocol becomes:
- Sender announces a session: file name, file size, chunk size, total chunk count.
- Receiver opens, says "ready for chunk N." Initially N=0.
- Sender pushes chunk N as a binary frame with an embedded index.
- Receiver writes the chunk, sends "received chunk N" acknowledgement.
- Both sides advance, repeat until total reached.
Each chunk is an idempotent operation. If anything dies, the receiver records its last acknowledged chunk and can ask the sender to resume from there.
Acknowledging at the right level
Critical detail: the receiver should acknowledge AFTER it has committed the chunk to durable storage (or at least to its OS-level write buffer that will survive the lifetime of the process), not just after it has received the bytes. Otherwise the sender might think a chunk landed safely when in fact the receiver crashed before the bytes ever hit disk.
File Tunnel persists last-acknowledged-chunk per receiver to its database, so a receiver that disconnects and rejoins picks up from the right offset even after a process restart.
Random-access writes vs. streaming writes
How the receiver writes chunks to disk matters for resume semantics:
StreamSaver / standard download API
Bytes are appended in order. You cannot seek backwards. If the receiver disconnects halfway, you can ask the sender to start again from chunk 0, OR you must accept restart cost.
File System Access API (Chrome/Edge)
The browser obtains a file handle to a user-chosen location with random-access write permission. Each chunk can be written at its correct offset (chunk_index * chunk_size). When the receiver reconnects, it can resume by asking for the next chunk, and writes land in the right place. This is the difference between "resume from chunk 47" working and not working.
What can go wrong, and how to handle it
Browser tab closed
Sender or receiver tab closed → WebSocket drops. With last-acknowledged chunk persisted, reopening the same session and code resumes from the correct offset.
Mobile network handoff
Switching from Wi-Fi to LTE drops TCP. The reconnect logic with exponential backoff (1s, 2s, 4s, ...) re-establishes the WebSocket and resumes the chunk request loop.
Sender goes offline mid-transfer
Receiver waits in "ready for chunk N" state. When the sender reconnects with the same code, the relay forwards the receiver's outstanding request and the transfer continues.
Computer suspends to sleep
On wake, browsers reopen sockets. WebSocket reconnect kicks in. Same outcome as the network handoff case.
Chunk corruption
Per-chunk hashes (BLAKE3) let receivers detect a corrupted chunk and request a re-send for that specific chunk, without restarting the whole transfer.
Operating system considerations
macOS and Linux have stable file-write semantics; an in-progress file written via the File System Access API is persisted on close. Windows behaves similarly. Mobile OS sandboxes can be more restrictive — both iOS and Android limit long background runs of browser tabs, so on mobile a transfer is most likely to succeed if the user keeps the page in the foreground.
Best practices for users
- Use Chrome or Edge on desktop for large transfers. The File System Access API enables true random-access resume.
- Plug in to power before starting an hours-long transfer.
- On Wi-Fi, prefer a wired connection or 5 GHz Wi-Fi near the router.
- Keep both tabs in the foreground if the transfer is long enough that browser memory pressure could kick in.
- For very large transfers, choose a longer expiry window so a reconnect after sleep is still in time.
For developers: implementing resume
If you're building this yourself, the minimum viable spec:
- Pick a chunk size big enough to amortize overhead (4 MB works), small enough to fit in memory comfortably.
- Embed the chunk index in the binary frame (4 bytes little-endian prefix is fine).
- Persist the receiver's last acknowledged chunk on every ACK.
- On reconnect, the receiver reads the persisted offset and requests the next chunk.
- Make every operation idempotent — duplicate ACKs and duplicate chunk sends should be safe.
Get those right and your transfers survive Wi-Fi handoffs, sleep/wake cycles, and the occasional cosmic ray.