fix(plugin-form): EmbeddableForm 致谢重定向按 host mount 导航 (#5112) - #5173
Merged
Conversation
…mount
An in-app `thankYouPage.redirectUrl` was travelled to with an unconditional
`window.location.href = url`, which resolves a rooted path against the ORIGIN
root — so under a host mounted at a sub-path (the console runs at basename
`/_console`) a same-origin destination such as `/thanks` left the application.
This is objectui#4989 defect 4 on the key that card did not cover.
The executor now splits by who can travel to the destination:
- app-relative -> the host's `HostNavigationContext` navigate when a host
supplied one, else today's browser navigation;
- anything else -> browser-level navigation, unconditionally.
The second arm is the seam's own declared input contract (`to` is an
application-relative path, "never an absolute URL"), which makes it structurally
impossible for a cross-origin URL to reach a host router: a relative reference
cannot carry an authority, so it always resolves to the base's origin.
`isRedirectUrlSafe` / `allowedRedirectHosts` — WHICH destinations are followed —
are untouched, and a refused destination reaches neither the seam nor the
browser. objectstack#7496's relative-only rule stays on `submitBehavior.url`.
Refs #5112
Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
The guide already documented WHICH destinations `thankYouPage.redirectUrl` admits; it said nothing about who travels to them, which is the half that decides whether a mounted host keeps the submitter inside the application. Adds the destination/traveller table, the `HostNavigationProvider` wiring, and the two reasons an external or a same-origin ABSOLUTE destination stays a browser navigation. Refs #5112 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Collaborator
Author
|
PM 验收:ACCEPT(objectui 分片 PM,session 实物核验(merge-base 裁定要点:
CI 亲读:21 项中 19 项终态全 success/skipped 零失败(Type Check / Lint / Build & E2E / shard 2、3 均绿),shard 1/4 在跑,按尾格零失败先例交 auto-merge 门。席位本地已过 plugin-form 全包 571 例。 处置:undraft + auto-merge(SQUASH)。若后续 CI 转红,由后继 PM 按标准返工流程处理。 Generated by Claude Code |
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.
Fixes #5112
问题
EmbeddableForm的致谢重定向落在一句无条件的浏览器级导航上(EmbeddableForm.tsx,window.location.href = pendingRedirect.url)。这对本键故意放行的外部目的地是对的,对它同样放行的 in-app 目的地是错的:
isRedirectUrlSafe的第一臂(
new URL(rawUrl, window.location.href)后url.origin === window.location.origin)放行相对路径
/thanks,而location.href把 rooted path 解析到 origin 根。于是在挂载于子路径的 host 下(framework CLI 为每个 embedded 部署都配一个;console 跑在
basename
/_console),提交者被送出应用,通常落到 host 自己的 404。两侧授权都合法、哪里都不报错,所以失败是静默的。
这就是 objectui#4989 defect 4 的同形,落在那张卡明确未覆盖的键上;修它所需的 seam
(
HostNavigationContext,@object-ui/react)已由 PR #5111 落地。改动
新增
packages/plugin-form/src/thankYouRedirectNavigation.ts,把「谁去执行导航」按目的地形态拆臂,
EmbeddableForm原来的内联 effect 换成这个 hook:/thanks、thanks、?ok=1、#done):host 提供了 navigate就走 seam,挂载的 host 因此把它放进自己的 mount;没有 supplier 时逐字节保持原行为
—— 无 router 的 host 没有 basename,origin 根解析本来就是对的。
allowedRedirectHosts放行的跨源):无条件保持浏览器级导航。第二臂不是保守,而是 seam 自己声明的入参契约:
HostNavigationValue.navigate写明to是"an already-resolved, application-relative path, never an absolute URL"。host navigate 是
客户端 router 跳转,跨源地址不是它该尝试的东西。
相对 vs 外部的判据(出处与钉子)
判据即 seam 入参契约在字符串上的复述:目的地是不是 relative reference。实现不手写
scheme 文法,交给 URL parser 判——同一个字符串对两个不同 base(
https://a.invalid//https://b.invalid/)解析,各自回到各自 base 的 origin 才算相对;自带 scheme 或自带authority(含 protocol-relative
//host/x)会忽略 base,两次得到同一个 origin。由此得到一条结构性不变量,已单独钉住:host seam 永远不可能拿到跨源 URL —— 相对
引用按 RFC 3986 无法携带 authority,必然解析回 base 的 origin。
同源绝对 URL 是两条命名臂都没覆盖的第三种形态,本 PR 把它归入浏览器导航,并写明这是
主动裁决而非遗漏:把它送进 seam 意味着本包把作者写全的地址改写成路径、再由挂载 router 放到
另一个地址上;作者写全了地址就是要那个地址。本卡要修的是「作者根本没写 origin、也没有
途径表达 mount」的那种失明。
明确不动
isRedirectUrlSafe与allowedRedirectHosts(哪些目的地被放行)原样未动:准入集(same-origin OR 作者 allowlist)是这个键自己的契约,不是本卡的;objectstack#7496 的
relative-only 规则留在
submitBehavior.url上,没有被引到这个键。被拒的目的地既到不了seam 也到不了浏览器。#5049 的等待归属与 #5073 的面板文案原样保留:unmount 与
「Submit Another Response」照旧取消在途重定向,有无 seam 皆然。
验证
新增
EmbeddableForm.redirectHostNavigation.test.tsx(14 例),四钉齐备:相对 + seam ⇒host navigate 收到且浏览器未导航;相对 + 无 seam ⇒ 回落现行为;外部 allowlist + seam ⇒
仍浏览器导航且 seam 未被调用;
isRedirectUrlSafe判决集重新测量不变。另含挂载放置演示、latest-wins navigate、unmount 取消、不缩短声明等待、同源绝对 URL 走浏览器臂、被拒目的地
两侧皆不到达。
注:全新 worktree 里
type-check先报一批TS2307 Cannot find module '@object-ui/*'(
submitRedirectNavigation.ts这类未改文件同样报),是 dist 尚未构建所致;pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-form^...' build之后全绿。反向验证(先预判,后测量)
预判:把执行点还原成无条件
window.location.href = url(删掉整个 seam 读),恰好三例转红——即断言 host navigate 收到目的地的那三例;其余 11 例与三个同文件既有测试套件全绿,
因为它们描述的是本来就正确、且本 PR 刻意未改的行为。
实测一致:
已
git checkout --还原。Generated by Claude Code