Conversation
…regression Introduce useSeededPermissions hook that reads host-supplied userCapabilities from window.GBKit and preseeds the @wordpress/core-data store. Cross-origin editor hosts cannot read the REST Allow header without Access-Control-Expose-Headers, which as of core-data 7.42.0 causes canUser to return false and hides the upload button. Seeding bypasses the inference path entirely when the host supplies authoritative capability information. Also extends the wp-env CORS mu-plugin to expose Allow and route REST OPTIONS through WP's dispatcher so the local environment matches production CORS behaviour.
Adds a non-optional `userCapabilities` field to `GBKitGlobal` on both iOS and Android so the serialized `window.GBKit` always exposes a `userCapabilities` object to the JS editor. The field is sourced from `EditorConfiguration`, which gains an optional `userCapabilities` (defaulting to `UserCapabilities(uploadFiles: false)`) and a builder setter on both platforms. Host apps that don't set it compile unchanged and get the opt-out default. This lets the JS `useSeededPermissions` hook preseed `canUser` attachment results when the host declares upload capability, avoiding the cross-origin `canUser` regression in @wordpress/core-data 7.42.0 where a missing `Allow` header now returns `false` instead of `undefined`.
Removes the default `UserCapabilities(uploadFiles: false)` on both iOS `EditorConfiguration`/`EditorConfigurationBuilder` and Android `EditorConfiguration.Builder`/`builder(...)`. Host apps must now supply `userCapabilities` explicitly when constructing a configuration. The previous default silently opted every host into `uploadFiles = false`, hiding the attachment upload button whenever a host forgot to set the field. Making the parameter required turns that oversight into a compile-time error so every integration consciously declares what the signed-in user can do, which is the whole point of the seeded-permissions pathway added in 4ffe117. Updates demo apps, tests, and the iOS/Android integration + preloading docs to pass `userCapabilities` everywhere. BREAKING CHANGE: `EditorConfiguration.init`/`EditorConfigurationBuilder.init` (iOS) and `EditorConfiguration.Builder`/`EditorConfiguration.builder(...)` (Android) no longer default `userCapabilities`. Pass `UserCapabilities(uploadFiles: <Bool>)` at every call site.
The iOS and Android demo apps were hardcoding `UserCapabilities(uploadFiles: true)`
for authenticated accounts, which skips the whole point of having capabilities
seeded from the host: the demo should be a reference for what a host app is
expected to do, and real hosts look up the signed-in user's capabilities rather
than asserting them.
Adds a `loadUserCapabilities` helper on both platforms that calls
`GET /wp/v2/users/me?context=edit` via the wordpress-rs client that each demo
already uses for routing/post-type discovery, then pulls `upload_files` out of
the response and passes it through to `UserCapabilities`. Falls back to
`uploadFiles = false` on error — if we can't confirm the user can upload, we
opt them out rather than silently enable it.
- iOS: `client.users.retrieveMeWithEditContext()` →
`user.capabilities[.uploadFiles]`
- Android: `client.request { it.users().retrieveMeWithEditContext() }` →
`data.capabilities[UserCapability.UploadFiles]`
…silently defaulting Drop the error-path fallbacks in the demo apps' loadUserCapabilities so a missing account, nil API client, or failed request surfaces as an error instead of silently opting the user out of uploadFiles. The legitimate `?? false` on the capability-map lookup stays — that's the "user responded but doesn't have the capability" case, not an error.
|
Noting that I'm landing I believe the seeding approach of this PR is still worth exploring. The patch in #691 unblocks updates and assumes the user has upload permissions; seeding would improve correctness by checking user permissions. When we re-engage seeding, we should...
It's worth investigating if the third item impacts discussed efforts to lower post saving down from native host → GBK's WebView (rely upon Gutenberg core saving mechanisms). E.g., will "this Author may edit their own post but not someone else's" pose a problem. If it does, options 3, 4, and 5 outlined in #691 may present alternatives for addressing it. More details on various approaches considered can be found in the "Agent details" section of #691. |
|
Follow up to #462 (comment): I ultimately replaced the patch with a middleware. Doing so addressed review findings where our patch approach may have erroneously flipped valid denials to allows. It also helps us avoid adding yet another patch to maintain. This PR's seeding approach remains worthwhile for providing accurate (not optimistic) capability values for cross-origin contexts. However, it appears the resolver parameters this PR utilizes for seeding have already changed in upstream Gutenberg. We'll need to reconcile that if we continue forward with that approach. An alternative worth considering: we could preload the capabilities alongside the existing preloading, that would negate the need for new bridge APIs. I captured the current state of things and alternative details below. AI-generated details
The store seeding will stop working after the next Alternative: preload
That gives real per-user upload permission on every app surface with no bridge API change, and real entity permissions for the preloaded post as a bonus. Both WordPress apps appear to go through One thing to verify first: what the namespaced API returns for an authenticated native Meanwhile, #691 covers upload with an |
What?
Introduces a
useSeededPermissionshook that reads host-supplieduserCapabilitiesfromwindow.GBKitand preseeds the@wordpress/core-datastore socanUser( ..., { kind: 'postType', name: 'attachment' } )returnstruewithout an OPTIONS request. Extends the native iOS/Android SDKs with a matchingUserCapabilitiestype that flows throughEditorConfigurationintoGBKitGlobal, and extends thewp-envCORS mu-plugin to expose theAllowheader and route RESTOPTIONSthroughWP_REST_Serverso the local environment matches production CORS behaviour.Breaking change:
userCapabilitiesis a required parameter onEditorConfiguration.init/EditorConfigurationBuilder(iOS) andEditorConfiguration.Builder/EditorConfiguration.builder(...)(Android). Every call site must passUserCapabilities(uploadFiles: <Bool>)explicitly — a missing argument becomes a compile error rather than a silentuploadFiles = falsedefault that hides the upload button.Why?
@wordpress/core-data7.42.0 (Gutenberg #76307) changedcanUserto returnfalseinstead ofundefinedwhen the RESTAllowheader is missing. This breaks@wordpress/editor'shasUploadPermissions = canUser(...) ?? truefallback — whencanUserreturnsfalse,??doesn't fall through,settings.mediaUploadbecomesundefined,MediaUploadCheckevaluates falsy, and the Upload button disappears from every MediaPlaceholder.GutenbergKit hits this in production, not just in wp-env. The editor is a local bundle (
bundle:///appassets.androidplatform.net) and the REST API lives on the user's WordPress site, so every request is cross-origin. Browsers hide response headers from JS unless the server lists them inAccess-Control-Expose-Headers, and WordPress core's defaultrest_send_cors_headers()only exposesX-WP-Total, X-WP-TotalPages, Link— notAllow. Confirmed empirically againsthttps://vanilla.wpmt.co/wp-json/wp/v2/media.Fixing WP core's CORS allowlist is a Trac-level change with a multi-release timeline, and reverting #76307 upstream is unlikely. This PR must land before the pending Dependabot bump at #456 (
@wordpress/editor14.41.0 → 14.44.0), otherwise every cross-origin GBKit host silently regresses.Required-vs-optional: an earlier iteration made
userCapabilitiesoptional with auploadFiles = falsedefault. That silently opted every un-updated host into the broken behaviour — the opposite of the fix. Making the parameter required forces every integration to consciously declare what the signed-in user can do, which is the whole point of the seeded-permissions pathway.How?
What we explored
@wordpress/core-datato restore theundefinedbehaviour. ❌ Fragile, drifts on every upstream bump, ignores upstream intent.canUserfrom host-declared capabilities. ✅ The host already authenticates the user and knows their capabilities viaGET /wp/v2/users/me?context=edit&_fields=capabilities. PassinguserCapabilitiesinto the editor via the existingwindow.GBKitbridge and dispatchingreceiveUserPermissions+finishResolutionsinto the core-data store bypasses the OPTIONS inference entirely.Changes
JS
src/components/editor/use-seeded-permissions.js: new hook readinguserCapabilities.uploadFilesfromgetGBKit()and dispatching seed data into the core-data store on mount. Uses only public core-data actions (receiveUserPermissions,finishResolutions) — no patches. Additive: if the host doesn't supply capabilities, the editor falls back to today's path.src/components/editor/index.jsx: callsuseSeededPermissions()before<EditorProvider>mounts.src/utils/bridge.js: documents the newuserCapabilitiesfield on theGBKitConfigtypedef.wp-env/mu-plugins/gutenbergkit-cors.php: exposesAllowto cross-origin JS; routes RESTOPTIONSthrough WP's dispatcher (non-RESTOPTIONSare still short-circuited).Native SDKs
UserCapabilitiestype on both iOS (Swift) and Android (Kotlin) with a single requireduploadFiles: Boolfield (designed to grow).EditorConfigurationon both platforms carries a requireduserCapabilities, which flows throughGBKitGlobalinto the JS bridge.EditorConfigurationBuilder(iOS) andEditorConfiguration.Builder/EditorConfiguration.builder(...)(Android) requireuserCapabilitiesas a constructor argument — a host app that forgets to supply it gets a compile error telling it exactly what to add.docs/integration.md/docs/code/preloading.mdupdated to passuserCapabilitiesat every call site.Host-app changes (fetching capabilities after auth and injecting into the
window.GBKitpayload) are out of scope here and will ship as separate coordinated PRs on WordPress iOS/Android and Jetpack iOS/Android.Testing Instructions
make test-js— all 136 existing tests plus 5 newuseSeededPermissionstests passmake test-swift-package— passes cleanmake test-android— passes cleanmake lint-js-fix— cleanmake build— builds without errorsnpx playwright test image-upload audio-upload video-upload file-upload cover-block gallery-block— all passwindow.GBKit.userCapabilities = { uploadFiles: true }via devtools before the editor mounts; verify Upload button appears in the Image blockuserCapabilitiesentirely; verify editor falls back to existingcanUserresolver path without crashing/wp/v2/mediawhen the seed is active (Network tab)UserCapabilities(uploadFiles: true)through the authenticated flow anduploadFiles: falsethrough the bundled/offline flowRelated issues