Skip to content

fix: let a failed oEmbed request fail - #764

Merged
jkmassel merged 3 commits into
trunkfrom
fix/oembed-failure-rejects
Oct 6, 2026
Merged

jkmassel merged 3 commits into
trunkfrom
fix/oembed-failure-rejects

Conversation

@jkmassel

@jkmassel jkmassel commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Extracted from #747; nothing here depends on it.

What?

Fixes a bug where a video uploaded through the VideoPress block could end up as an empty box holding a link. A failed oEmbed request now fails; the editor used to answer it with a made-up response.

Why?

The editor caught every failed oEmbed request and resolved with a response holding a link to the URL. Core stored that as a finished preview, so a block that polls for its preview never saw the failure.

After a VideoPress upload, the first /oembed/1.0/proxy request returns 404 until WordPress.com can embed the new video. Prior to this PR the block took the link for a preview, stopped asking, and rendered it in its player sandbox: an empty box, with the link saved into the block's cacheHtml. Now core stores false, as it does for a failed preview everywhere else, and the block asks again.

How?

transformOEmbedApiResponse in src/utils/api-fetch.js no longer catches a failed request.

The core Embed block behaves as before. It treats false and a link fallback the same way, and shows "could not be embedded" for both.

Successful responses are untouched. A link fallback that WordPress itself returns still passes through, and the wrapper is still stripped from YouTube, Vimeo, Dailymotion and TED embeds.

Testing Instructions

  • make test-web-unit — 333 tests, 4 of them new. The two that expect a rejection fail on trunk without the fix: AssertionError: promise resolved "{ …(3) }" instead of rejecting.
  • On a site with VideoPress (a WordPress.com site works), upload a video with the Video block: once WordPress.com has processed it, the block shows the player, not an empty box holding a link.
  • Paste a URL WordPress can't embed into an Embed block: it shows its "could not be embedded" notice, as it does on trunk.

`transformOEmbedApiResponse` caught every failed oEmbed request and
resolved with a made-up response holding a link to the URL. Core then
stored that as a finished preview.

A block that polls for its preview never saw the failure. After a
VideoPress upload, the first `/oembed/1.0/proxy` request returns `404`
until WordPress.com can embed the new video. The block took the link for
a preview, stopped asking, and rendered it in its player sandbox: an
empty box, with the link saved into the block's `cacheHtml`.

The middleware now leaves a failed request to reject, so core stores
`false`, as it does everywhere else. The core Embed block is unaffected:
it treats `false` and a link fallback the same way and shows "could not
be embedded" for both. A link fallback that WordPress itself returns
still passes through, and the wrapper stripping for YouTube, Vimeo,
Dailymotion and TED embeds is unchanged.
@jkmassel jkmassel added the [Type] Bug An existing feature does not function as intended label Oct 5, 2026
@jkmassel jkmassel self-assigned this Oct 5, 2026
@jkmassel
jkmassel requested a review from dcalhoun October 5, 2026 16:21
@jkmassel
jkmassel marked this pull request as ready for review October 5, 2026 16:21
@wpmobilebot

wpmobilebot commented Oct 5, 2026 •

Copy link
Copy Markdown

XCFramework Build

This PR's XCFramework is available for testing. Add the following to your Package.swift:

.package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/764")

Built from e3a4028

@dcalhoun dcalhoun 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.

Thanks for addressing this. The changes tested well for me. The inline suggestions are small and not blocking.

);
}

it( 'rejects when WordPress cannot embed the URL', async () => {

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.

Finding from Claude:

The E2E test should show a fallback link for a non-embeddable URL (e2e/embed-content.spec.js:110) still describes the removed fallback, and it passes with this fix reverted: it only waits for attributes.url, which is set on submit, before the request settles. Rename it and assert the stored preview so it guards this change?

await page.waitForFunction(
  (url) => window.wp.data.select("core").getEmbedPreview(url) === false,
  "https://example.com/not-embeddable",
  { timeout: 30_000 },
);

Comment thread src/utils/api-fetch.js Outdated
Comment on lines +456 to +460
* A failed request is left to reject. Core stores `false` for it, which the
* Embed block shows as "could not be embedded" and which blocks that poll for a
* preview — VideoPress, while WordPress.com is still processing an upload —
* take as the cue to ask again. Resolving with a link in its place reads as a
* finished preview, so those blocks stop asking and render the link.

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.

Optional: trim this to the final-state "why". The last sentence argues against the removed approach, which fits the commit message better.

Suggested change
* A failed request is left to reject. Core stores `false` for it, which the
* Embed block shows as "could not be embedded" and which blocks that poll for a
* preview — VideoPress, while WordPress.com is still processing an upload —
* take as the cue to ask again. Resolving with a link in its place reads as a
* finished preview, so those blocks stop asking and render the link.
* A failed request is left to reject, so core stores `false` and blocks that
* poll for a preview (e.g. VideoPress while processing an upload) ask again.

The E2E test for a non-embeddable URL was named for a fallback link the
editor no longer makes up, and it waited only for the block's `url`
attribute. The Embed block sets that on submit, before the oEmbed
request settles, so the test passed whether or not a failed request was
replaced with a made-up response.

It now waits for core to store `false` for the preview. With the
middleware's catch restored, it times out there.
The comment also argued against the link fallback, which is gone. That
argument is in the commit that removed it.
@jkmassel
jkmassel enabled auto-merge (squash) October 6, 2026 22:50
@jkmassel
jkmassel merged commit b4a1cf6 into trunk Oct 6, 2026
24 checks passed
@jkmassel
jkmassel deleted the fix/oembed-failure-rejects branch October 6, 2026 22:57
jkmassel added a commit that referenced this pull request Oct 7, 2026
Thirteen of #747's fifteen commits as one, rebased onto the asset bundle
cache policy branch. It holds five changes:

- Uploads. The page sends media uploads to native code over
  `gbk-upload:`, a `WKURLSchemeHandler` in the editor's own web view, in
  4 MB `ArrayBuffer` chunks. iOS reclaims a suspended app's sockets, so
  the loopback server this replaces stopped answering after an ordinary
  screen lock. `MediaUploadServer` is deleted and its processor, uploader
  and client routing moves into `MediaUploadService`. Android keeps its
  loopback server: the middleware picks a transport from what `GBKit`
  advertises.
- `gbk-media-file:` responses are streamed in 1 MB chunks, a task WebKit
  has stopped is never answered, and a path outside the media directory
  is refused.
- The native inserter imports a picked item as a file and hands it to the
  page as a real `File`, through a file input that `NativeFileInput`
  answers. WebKit asks for that panel from iOS 18.4; before that the
  inserter hides the photo library and the camera.
- The page's REST requests are relayed through `gbk-rest:`, so a site that
  doesn't answer `Origin: file://` with CORS headers still loads under
  Lockdown Mode.
- Media uploads get a ten-minute inactivity timeout.

The fetch interceptor also leaves the editor's own URL schemes out of
network logging, and `docs/code/media-uploads.md` is new.

Breaking: `GBKitGlobal.init` takes `nativeUploadScheme:` and
`restRelayBaseURL:` instead of `nativeUploadPort:` and
`nativeUploadToken:`.

Not here, against #747 as it was:

- `GutenbergKitHTTP`, its tests, `GutenbergKitDebugServer` and the demo
  app's Media Proxy Server screen are no longer deleted. Nothing in
  GutenbergKit imports the library any more; removing it is its own
  change.
- The oEmbed fix is #764.
- The editor-assets slash fix landed on trunk in #573.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants