feat: add MediaUploader, for a host that owns the whole upload - #628
Draft
jkmassel wants to merge 1 commit into
Draft
feat: add MediaUploader, for a host that owns the whole upload#628jkmassel wants to merge 1 commit into
jkmassel wants to merge 1 commit into
Conversation
Performing a media upload — and retrying it — should be a single, all-or-nothing responsibility: either GutenbergKit performs the upload and owns its retries, or the host does. Both go to the same configured site; the only difference is who executes the requests. `MediaUploadDelegate.uploadFile` doesn't offer that. A host performs the `POST /wp/v2/media` and returns the raw response it received — then the editor, reading that response, drives the `post-process` retries and the orphan cleanup behind it, through the WebView rather than the host's stack. A host that took over uploads to run them through its own networking still didn't own the retries. It also receives no form fields, so an attachment it uploads lands unattached to its post. Add `MediaUploader`, which owns the upload end to end: - `upload(_:)` returns the finished attachment or throws. There is no raw response left for the editor to retry behind it, so the host drives its own post-process recovery and force-deletes its own orphan on terminal failure. - It receives a `MediaUpload` carrying the file, its metadata, the editor's non-file form fields (`post`, additionalData) and the request query (`?_embed`) — everything needed to reproduce a native request. - Fields are a `MediaUploadField` list rather than a dictionary, so repeated names (a `field[]` array) survive verbatim and in order. Purely additive. `uploadFile` still works and is marked deprecated, pointing hosts at the replacement; an uploader takes precedence when both are set. GutenbergKit's own build keeps one deprecation warning at the call site that supports the old hook — the marker exists to tell hosts to migrate, and supporting the hook until it is removed means calling it. With an uploader set, the delegate's metadata gate can no longer decline a file: the gate exists to skip a temp copy for a file the delegate won't touch, but an uploader takes over delivery for *every* file, so passing through would silently bypass it. Covered on both platforms. `MediaUploadServerTest` crosses Detekt's LargeClass threshold; baselined rather than split, which is its own change.
This was referenced Sep 5, 2026
XCFramework BuildThis PR's XCFramework is available for testing. Add the following to your .package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/628")Built from 0ab9ee5 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #627. Fifth of ten PRs splitting #621, and the heart of it. Purely additive — nothing existing changes shape.
What?
A
MediaUploaderprotocol that takes over performing a media upload on the host's own stack, and owns its whole lifecycle: retries, recovery, and cleanup.Why?
Performing a media upload — and retrying it — should be a single, all-or-nothing responsibility: either GutenbergKit performs the upload and owns its retries, or the host does. Both go to the same configured site; the only difference is who executes the requests.
MediaUploadDelegate.uploadFiledoesn't offer that. A host performs thePOST /wp/v2/mediaand returns the raw response it received — then the editor, reading that response, drives thepost-processretries and the orphan cleanup behind it, through the WebView rather than the host's stack. A host that took over uploads to run them through its own networking still didn't own the retries. It also receives no form fields, so an attachment it uploads lands unattached to its post.Neither is fixable while the hook returns a raw response, which is what the replacement changes.
How?
MediaUploaderupload(_:)returns the finished attachment or throws. There is no raw response left for the editor to retry behind it, so the host drives its ownpost-processrecovery and force-deletes its own orphan on terminal failure.MediaUploadCarries the file, its metadata, the editor's non-file form fields (
post, additionalData) and the request query (?_embed) — everything needed to reproduce a native request.MediaUploadFieldFields are an ordered list rather than a dictionary, so repeated names (a
field[]array) survive verbatim and in order. A named type rather than a tuple: tuples are not nominal, so a tuple-typed property would permanently blockEquatable/Hashable/Codablesynthesis onMediaUpload, and that is not fixable later without a source break for every host.Precedence
uploadFilestill works and is marked deprecated, pointing hosts at the replacement; an uploader takes precedence when both are set. #629 removes the old hook, so hosts get a migration window rather than a flag day.This deliberately leaves one deprecation warning in GutenbergKit's own build, at the call site that supports the old hook. The marker exists to tell hosts to migrate, and supporting the hook until it is removed means calling it. It goes away with #629.
The admission gate
With an uploader set, the delegate's metadata gate can no longer decline a file. The gate exists to skip a temp copy for a file the delegate won't touch, but an uploader takes over delivery for every file, so passing through would silently bypass it.
Testing Instructions
Five new tests on iOS, three on Android: delivery, form fields and query, precedence over
uploadFile, the declined-file case, and a terminal throw surfacing without GutenbergKit re-delivering.swift test— host suite green:Gutenberg:testDebugUnitTestgreenxcodebuildMediaUploadServerTestcrosses Detekt'sLargeClassthreshold; baselined rather than split, which is its own change.