Skip to content

fix(shell): bridge answers follow the shell's origin rule - #178

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/bridge-resolve-origin
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/bridge-resolve-origin

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:壳把桥调用的结果写回页面(__objectstackNativeResolve)时,只写给已配置服务器同源的页面,与壳其余携带数据的页面脚本(navigate、pushTokenChanged、connectionRemoved)同一条来源规则。
  • 为什么改:壳注入页面、携带数据的每段脚本都遵循同一条来源规则,调用结果的回写是其中一段。
  • 风险与回滚:服务器同源页面行为不变(同一文档内的路由变化、同源跳转后的新文档均照旧);主框架停在其他来源页面时,该次结果不投递。纯 JS 改动,OTA 可下发;回滚即 revert 本 PR。
  • 先看:lib/shell/bridge/scripts.ts 的 buildResolveScript;components/shell/ConsoleWebView.tsx 中 onMessage 的调用点;__tests__/lib/shell/bridge-scripts.test.ts 的 resolve script 组。
  • 两分钟验证:npx jest --ci __tests__/lib/shell/bridge-scripts.test.ts,预期 54 passed,其中 calls no resolve global on a page on another origin 五例全绿。
  • 需要你做的:无,席位落地。

What changes

  • buildResolveScript(result, serverUrl) (lib/shell/bridge/scripts.ts) follows the origin rule of buildNavigateScript, buildPushTokenChangedScript and buildConnectionRemovedScript: the script returns at once unless window.location.origin equals the configured server's origin (default ports dropped, via originOf). There it calls window.__objectstackNativeResolve with the result.
  • On any other origin the script reads window.location.origin and nothing else: no global of the page is called.
  • A server URL with no origin builds no script (null), as for the three sibling scripts.
  • ConsoleWebView passes serverUrl, the same value its bridge handler judged the call against, and injects nothing for a null script. The documentRef and excursion drops are unchanged.
  • The call into the resolve global is guarded by a typeof function check instead of a truthiness check.
  • Docs: docs/HYBRID-SHELL.md (Wire format, Security) and the BRIDGE_RESOLVE_GLOBAL doc comment state the rule.
  • Changeset .changeset/bridge-resolve-origin.md (patch).

Tests

__tests__/lib/shell/bridge-scripts.test.ts, new resolve script group:

  • settles the call on a page of the configured server's origin: server URL with a base path, the page's resolve global called once with the result.
  • compares the page origin with the default port dropped: server URL https://acme.example.com:443/os, page origin https://acme.example.com.
  • calls no resolve global on a page on another origin, five cases: another host, another scheme, another port, a subdomain, an opaque origin.
  • reads nothing but the origin of a page on another origin: the script's reads of the page, recorded through a Proxy, are exactly location and location.origin.
  • does nothing on a page of the server's origin that has no resolve global.
  • builds no script for an unusable server URL.
  • splices the result as escaped JSON.

Existing bootstrap script tests pass the server URL; the page helper now refuses a null script, so a missing script cannot pass silently. settles no call of a later document with an answer meant for an earlier one uses two documents on the server's origin; other origins are the new group's.

__tests__/components/ConsoleWebView.test.tsx, each running the injected answer in a fake page:

  • delivers an answer only to a page on the configured server's origin (new): settled on the server's origin, nothing on another.
  • still settles the call after a load start in the same WebView (an in-app navigation): the answer now also settles the call on the server's origin.
  • follows the new connection's origin: the host left gets no answer, the new one does: the answer settles on the new connection's origin only.

Ablation, on committed code, mutated through ablation-replace.mjs (anchor hit once, blob changed on disk) and restored to the HEAD blob with git diff HEAD empty:

  • Origin check removed: 8 red, the five another origin cases, reads nothing but the origin of a page on another origin, delivers an answer only to a page on the configured server's origin and follows the new connection's origin: the host left gets no answer, the new one does.
  • A first attempt was refused by the tool before any test ran (its replacement text was already inside the anchor, so the replacement count could not rise); the file was restored to the HEAD blob and that attempt has no reading. The second attempt replaced the check with a marker line.

Acceptance notes

  • Server-origin behaviour is unchanged on the paths measured: the same document after an in-app history change settles its call (pinned above); after a same-origin redirect the new document is answered and its own per-document call ids decide, unchanged.
  • npx prettier --check warns on lines in the touched files that read the same on main (gatedInstallId in scripts.ts; one listener in bridge-scripts.test.ts; several in ConsoleWebView.test.tsx). Left untouched; the added lines are prettier-clean.
  • Local jest ran with --maxWorkers=2 on a shared container; CI runs the plain command.

Verification

All at head 5028e6f (base e60866b), each through the shared verify lock, exit code captured before any pipe.

npx tsc --noEmit: exit 0
VERDICT command-exit 0, no compiler output
npx eslint . --ext .ts,.tsx: exit 0
VERDICT command-exit 0, no lint output
npx jest --ci --coverage --maxWorkers=2: exit 0
Test Suites: 44 passed, 44 total
Tests:       821 passed, 821 total
lib/shell/bridge/scripts.ts coverage: 100 statements, 100 branches, 100 functions, 100 lines
components/shell/ConsoleWebView.tsx coverage: 99.24 statements, 93.33 branches, 96 functions, 100 lines
Browser check, Chromium 153, two loopback origins
server origin, a scan() call answered:  the call settles with the value
another origin, the same answer:        no global of the page is called

Generated by Claude Code

A call's answer is spliced only into a page on the configured server's
origin, the same rule as the shell's other page scripts that carry data:
the resolve script checks window.location.origin against the server's
origin (default ports dropped) before it calls the resolve global, and
any other page gets nothing. An unusable server URL builds no script.

Claude-Session: https://claude.ai/code/session_01QSeKYnW1Xf6HDSWz1eGAqv
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv

Seat verdict: ACCEPT. Landed by the seat under the maintainer ruling on #160. Head-sha: 5028e6fe2a90649424f7786d1f5bafd7e2580a4d

维护者速读

  • 改了什么: 桥接应答按壳的统一来源规则送达。window.ObjectStackNative 调用的应答只交给配置服务器来源上的页面,默认端口忽略;主框架里的其他页面收不到。
  • 为什么改: 让应答与 navigate、pushTokenChanged、connectionRemoved 遵守同一条规则。
  • 风险与回滚: 本服务器页面上的行为不变,同源的应用内跳转实测与改动前一致。纯 JS 改动,revert 即可回滚。
  • 先看: lib/shell/bridge/scripts.ts 的 buildResolveScript,以及 ConsoleWebView.tsx 的 onMessage。
  • 两分钟验证: npx jest --ci __tests__/lib/shell/bridge-scripts.test.ts __tests__/components/ConsoleWebView.test.tsx
  • 需要你做的: 无需真机检查;细节已在会话中说明。

席位核对

  1. Shape: draft → main, no Fixes line (no-card order). Clause-②: no and Metadata-protocol: none are present; the changeset is patch and states the rule only.
  2. Gates: check-runs on head 5028e6f: Code Quality success and Lint & Type Check success. mergeable_state is clean. Local jest --ci --coverage passes 44/44 suites, 821 tests.
  3. Ablation: removing the origin rule turns 8 tests red.
  4. Landing: the path surface is NOT governed, +204 / −34. EAS guard on main e60866b: build and publish steps skipped. The seat marks the PR ready and enables auto-merge (merge queue).

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 3, 2026 01:33
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 668ed7e Nov 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant