attachment_engine_web 0.0.1-dev.0
attachment_engine_web: ^0.0.1-dev.0 copied to clipboard
The web implementation of attachment_engine. Share via the Web Share API, open-externally via window.open, paths via the Origin Private File System, download via fetch()+OPFS, and video/audio playback [...]
attachment_engine_web #
The web implementation of attachment_engine.
Status #
Development preview — not feature-complete. Uses only raw
dart:js_interop and dart:ui_web — Dart SDK core libraries, not even a
pub package, let alone a third-party plugin:
- Share (
shareText) — the browser's native Web Share API (navigator.share) - Open externally (
openExternally, andopenOfficePreviewfalling back to it) —window.open - Paths (
getApplicationSupportDirectory,getApplicationCacheDirectory) — the browser's real Origin Private File System (OPFS), vianavigator.storage.getDirectory() - Download (
startDownload,resumeDownload,cancelDownload,downloadEvents) —fetch()streamed chunk-by-chunk into OPFS, with anAbortControllerbacking cancellation - Video/audio playback (
video*/audio*) — real<video>/<audio>elements, driven directly viadart:js_interop, with the video surface embedded throughdart:ui_web'splatformViewRegistry
Paths on web — read this before relying on the returned strings #
The browser sandbox has no real absolute filesystem path the way
native platforms do. getApplicationSupportDirectory() and
getApplicationCacheDirectory() return a stable logical key —
/attachment_engine/support and /attachment_engine/cache respectively —
not a real OS path. It is only meaningful to this package's own
implementation: startDownload/resumeDownload interpret any path built
by joining one of these keys with / and a filename as a slash-separated
chain of OPFS directory names (created on demand), and write the
downloaded bytes there. Treat the returned string as an opaque handle to
pass back into this same implementation — do not display it to users or
hand it to unrelated APIs expecting a real filesystem path.
Download implementation notes #
fetch() does not expose progress natively; this implementation reads
response.body via a ReadableStreamDefaultReader chunk by chunk,
writing each chunk into an OPFS FileSystemWritableFileStream at an
explicit position (so both fresh downloads and Range-resumed downloads
land in the right place), and emits a progress event
({downloadId, type: 'progress', received, total, path}) after every
chunk — matching the same event shape used by the other platforms
(compare attachment_engine_windows's HttpClient-based implementation).
cancelDownload aborts the in-flight fetch() via a real
AbortController.
Video/audio implementation notes #
videoBuildView registers one HtmlElementView view factory per
playerId (via dart:ui_web's platformViewRegistry.registerViewFactory)
that returns the same <video> DOM element the control methods
(videoPlay/videoPause/videoSeek/…) act on directly, so control calls
and the rendered view always stay in sync. Playback-state events
(timeupdate, playing, pause, waiting, ended, error,
loadedmetadata) are forwarded as {state, positionMs, durationMs, width, height} maps (video) or {state, positionMs, durationMs} (audio),
matching the shape emitted by the iOS (VideoPlatformView.swift/
AudioChannel.swift) and Android implementations. Audio has no visual
surface, matching the other platforms — there is no audioBuildView.
Not yet implemented (throws UnimplementedError) #
shareFile— the Web Share API needs an in-memoryFile/Blob, not a filesystem path; this platform interface's path-based contract doesn't carry that. Sharing an actual file needs a byte-based contract addition.pdfOpen/pdfRenderPage/pdfClose— deliberately left unimplemented rather than forced in. The task explicitly authorized using PDF.js (Mozilla's PDF renderer) as a one-off exception to this repo's otherwise-strict "flutter.dev/dart.dev-published or SDK-core dependencies only" rule, since PDF.js is neither. After investigation, wiring it in cleanly turned out to be substantially riskier than the other three capabilities, for concrete reasons:- PDF.js's worker (
pdf.worker.mjs) must be reachable at a URL the main thread can hand topdfjsLib.GlobalWorkerOptions.workerSrcbeforegetDocument()is called. Vendoring the library files as Dart package assets (lib/assets/…) does not by itself make them fetchable by the browser at a stable, predictable URL — Flutter web plugin assets only land underassets/packages/<pkg>/…in the final build output, and there is no supported way for a plugin's Dart code to guarantee the consuming app'sindex.htmlhas already loadedpdf.js(as a<script>tag) before this package's Dart code runs, short of asking every consuming app to hand-edit theirweb/index.html— which defeats the "just add the dependency" ergonomics the other three capabilities have. - That leaves runtime script-injection (creating a
<script src=...>tag from Dart and awaiting itsloadevent) as the only fully-automatic option, which adds a real source of flakiness (asset-serving path assumptions, load-order races with the worker script, and behavior differing betweenflutter run -d chromeand a realflutter build webrelease bundle) that could not be verified end-to-end in this environment (no Chrome/chromedriver available here to actually exercise a rendered PDF page). - Given the choice between shipping something fragile and undertested
for PDF versus keeping the other three capabilities solid, PDF was
left as a documented follow-up. A future implementation should
either (a) vendor
pdf.js/pdf.worker.mjsunderlib/assets/pdfjs/, declare them influtter: assets:, and inject a<script>tag pointing at the built asset path with aCompleterawaiting itsloadevent before callingpdfjsLib.getDocument, or (b) document that the consuming app must add the PDF.js<script>tag to its ownweb/index.html(the same convention used by plugins that wrap a JS library requiring page-load-time setup) — and pick whichever is verified against a real browser.
- PDF.js's worker (
- Embedded webview — out of scope for every platform in this
contract; handled by the official
webview_flutterpackage at the app-facing layer instead.
You should not need to depend on this package directly — add
attachment_engine to your app instead, and the
right platform implementation is selected automatically.