Skip to content

feat(web): add pluggable storage backends for RDP file downloads#1221

Merged
Benoît Cortier (CBenoit) merged 1 commit into
Devolutions:masterfrom
gabrielbauman:gbauman/opfs-storage-backend-pr
Jun 23, 2026
Merged

feat(web): add pluggable storage backends for RDP file downloads#1221
Benoît Cortier (CBenoit) merged 1 commit into
Devolutions:masterfrom
gabrielbauman:gbauman/opfs-storage-backend-pr

Conversation

@gabrielbauman

@gabrielbauman Gabriel Bauman (gabrielbauman) commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Right now, RdpFileTransferProvider accumulates every download chunk in a Uint8Array[] in memory. For a 500 MB file, that means ~1 GB of RAM (the chunks plus the final Blob copy). This PR introduces a FileStorageBackend abstraction with two implementations so we can do better when the browser supports it.

What's new

BlobStorageBackend - wraps the existing in-memory chunk accumulation behind the new interface. This is the universal fallback and behaves identically to the old code path.

OpfsStorageBackend - streams download chunks directly to the Origin Private File System via FileSystemWritableFileStream. Peak RAM drops from ~2x file size to roughly one chunk (~64 KB). On finalize, getFile() returns a lazy File handle backed by disk -- no bulk read into memory.

Automatic detection - by default, the first download triggers an OPFS probe (create file, open writable, close, delete). If it works, OPFS is used for the session. If not, Blob kicks in silently. Consumers can also force a backend via the new storageBackend option on RdpFileTransferProviderOptions:

// Auto-detect (default)
new RdpFileTransferProvider({ storageBackend: 'auto' })

// Force in-memory
new RdpFileTransferProvider({ storageBackend: 'blob' })

// Bring your own backend
new RdpFileTransferProvider({ storageBackend: myCustomBackend })

OPFS housekeeping

  • Each session gets its own subdirectory under ironrdp-transfers/ in the OPFS root.
  • On startup, stale session directories older than 24 hours (from crashed tabs, etc.) are cleaned up in the background.
  • On dispose, the session directory is deleted recursively. If that fails, individual files are cleaned up as a fallback.
  • Quota exhaustion surfaces as an actionable error on the download, not a silent failure.

Browser support

The writable stream APIs this depends on reached Baseline across all major browsers in September 2025 (Chrome 86+, Firefox 111+, Safari 17.2+, Edge 86+). Older browsers get the Blob fallback automatically via the probe -- no breakage.

API surface

All new types are re-exported from the package entry point:

  • FileStorageBackend (interface)
  • FileWriteHandle (interface)
  • StorageBackendPreference (type: 'auto' | 'blob')
  • BlobStorageBackend (class)
  • OpfsStorageBackend (class)
  • detectStorageBackend (function)

The only change to the existing public API is the new optional storageBackend field on RdpFileTransferProviderOptions. No breaking changes.

Testing

97 tests covering both backends, detection/fallback logic, download flow integration, sanitization of adversarial file names, dispose/abort lifecycle edge cases, and error paths (quota, timeouts, init failures). No new runtime dependencies.

Copilot AI review requested due to automatic review settings April 15, 2026 20:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a pluggable download storage layer to iron-remote-desktop-rdp so large RDP file downloads can stream to persistent storage (OPFS) instead of buffering all chunks in RAM, while keeping a universal in-memory Blob fallback.

Changes:

  • Introduces FileStorageBackend / FileWriteHandle abstractions plus BlobStorageBackend and OpfsStorageBackend implementations.
  • Adds detectStorageBackend() and re-exports new storage APIs from the package entry point.
  • Refactors RdpFileTransferProvider download path to write chunks via a backend and adds extensive Vitest coverage.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
web-client/iron-remote-desktop-rdp/src/storage/storage.test.ts New unit tests for Blob/OPFS backends and backend detection.
web-client/iron-remote-desktop-rdp/src/storage/index.ts Barrel exports for the new storage APIs.
web-client/iron-remote-desktop-rdp/src/storage/detect.ts Implements auto-detection logic (OPFS probe with Blob fallback).
web-client/iron-remote-desktop-rdp/src/storage/OpfsStorageBackend.ts OPFS streaming backend + session directory lifecycle and stale cleanup.
web-client/iron-remote-desktop-rdp/src/storage/FileStorageBackend.ts Defines the backend and write-handle interfaces (public API types).
web-client/iron-remote-desktop-rdp/src/storage/BlobStorageBackend.ts In-memory fallback backend mirroring prior behavior.
web-client/iron-remote-desktop-rdp/src/main.ts Re-exports storage types/classes/functions for package consumers.
web-client/iron-remote-desktop-rdp/src/RdpFileTransferProvider.ts Integrates storage backend into download flow and adds new option validation.
web-client/iron-remote-desktop-rdp/src/RdpFileTransferProvider.test.ts Adds tests for storageBackend option validation and download integration/error paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread web-client/iron-remote-desktop-rdp/src/storage/OpfsStorageBackend.ts Outdated
Comment thread web-client/iron-remote-desktop-rdp/src/RdpFileTransferProvider.ts
Comment thread web-client/iron-remote-desktop-rdp/src/storage/detect.ts
Comment thread web-client/iron-remote-desktop-rdp/src/storage/FileStorageBackend.ts Outdated
@gabrielbauman Gabriel Bauman (gabrielbauman) force-pushed the gbauman/opfs-storage-backend-pr branch 3 times, most recently from febac42 to d9213c5 Compare April 15, 2026 21:17

@CBenoit Benoît Cortier (CBenoit) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heads up: I intended to review this sooner, but ran out of time, I'm sorry! I'll be away until next week, I'll get through it then =)

@ymarcus93

Copy link
Copy Markdown
Contributor

Hey Benoît Cortier (@CBenoit), gentle bump, do you mind taking a look at this PR?

@CBenoit

Copy link
Copy Markdown
Member

Yuval Marcus (@ymarcus93) Ping acknowledged. I’ll go through important PRs, including this one by Friday.

@CBenoit Benoît Cortier (CBenoit) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m sorry for the delay, I finally went through this! It’s looking pretty good to me 👍

Yuval Marcus (@ymarcus93) are you in position of pushing a commit to fix the conflict, or should I handle that?

@ymarcus93

Yuval Marcus (ymarcus93) commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

I’m sorry for the delay, I finally went through this! It’s looking pretty good to me 👍

Yuval Marcus (Yuval Marcus (@ymarcus93)) are you in position of pushing a commit to fix the conflict, or should I handle that?

Thank you!

I can handle the conflict, no worries. Will wait for #1381 to be merged first just in case

@ymarcus93

Yuval Marcus (ymarcus93) commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Hmm actually, I don't think I can since I don't own the source branch. I could make a duplicate PR thats cherry-picked and rebased with conflicts resolved

@CBenoit

Copy link
Copy Markdown
Member

Thank you for proposing; I think it will be fine, Gabriel Bauman (@gabrielbauman) enabled maintainers to push to his branch, I’ll try that.

Replace the in-memory Uint8Array[] chunk accumulation in
RdpFileTransferProvider with a FileStorageBackend abstraction that
supports two implementations:

- BlobStorageBackend: in-memory accumulation (universal fallback,
  same behavior as before).
- OpfsStorageBackend: streams chunks to the Origin Private File
  System, reducing peak download RAM from ~2x file size to ~chunk
  size (typically 64 KB).

Backend selection is automatic by default: a full OPFS round-trip
probe detects browser support at first download, falling back to
Blob transparently.  Consumers can override via the new optional
storageBackend field on RdpFileTransferProviderOptions, passing
'auto', 'blob', or a custom FileStorageBackend instance.

The OPFS backend manages per-session temp directories under
ironrdp-transfers/, cleans up stale sessions from crashed tabs on
startup, and handles quota exhaustion with actionable errors.

All new types (FileStorageBackend, FileWriteHandle,
StorageBackendPreference, BlobStorageBackend, OpfsStorageBackend,
detectStorageBackend) are re-exported from the package entry point.

No new runtime dependencies.  97 tests cover both backends,
detection logic, download flow integration, and edge cases.
@CBenoit Benoît Cortier (CBenoit) force-pushed the gbauman/opfs-storage-backend-pr branch from 8b3c7e0 to 12ac73c Compare June 23, 2026 18:29
@CBenoit Benoît Cortier (CBenoit) enabled auto-merge (squash) June 23, 2026 18:30
@CBenoit

Copy link
Copy Markdown
Member

Looks like I was able to address the conflicts on my side 🙂

Thank you Gabriel Bauman (@gabrielbauman)!

@CBenoit Benoît Cortier (CBenoit) merged commit af2706d into Devolutions:master Jun 23, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants