Skip to content

fix(runtime): plugin proxy must not be unintentionally Thenable#8473

Open
playopsstudio wants to merge 1 commit into
ionic-team:mainfrom
PlayOps-Studio-Org:fix-proxy-thenable-shortcircuit
Open

fix(runtime): plugin proxy must not be unintentionally Thenable#8473
playopsstudio wants to merge 1 commit into
ionic-team:mainfrom
PlayOps-Studio-Org:fix-proxy-thenable-shortcircuit

Conversation

@playopsstudio
Copy link
Copy Markdown

fix(runtime): plugin proxy must not be unintentionally Thenable

Summary

Adds case 'then', case 'catch', and case 'finally' to the plugin-proxy get-trap switch in core/src/runtime.ts, returning undefined. Aligns with the existing $$typeof short-circuit (which solves the equivalent React-framework-machinery problem; see comment at line 167).

Fixes #8472.

Mechanism (short form; full detail in linked issue)

JS engines treat any object with a callable .then as a Thenable. The proxy's get-trap returns a method-wrapper function for any property name not in the switch — including .then. So Promise.resolve(proxy) invokes proxy.then(resolve, reject), which dispatches a bogus "then" method to the native bridge → either "method not implemented" rejection OR indefinite hang (wrapper never invokes resolve/reject).

This bites code that holds a plugin proxy as a Promise resolution value — most commonly via the lazy-load pattern let p = import("some-plugin").then((m) => m.Plugin), which is a standard dynamic-import idiom for code-splitting Capacitor plugins on tree-shake-aware web targets.

Real-world impact

Three independent production failure surfaces in a private game-runtime codebase, all caused by this single bug — one developer-week of investigation before the root cause was identified. We shipped a consumer-side container-wrap workaround (Promise<{ plugin: PluginType }> instead of Promise<PluginType>), but the workaround is fragile (one missed wrap reintroduces the bug class).

Fix

3-line addition in core/src/runtime.ts; 1 regression test in core/src/tests/plugin.spec.ts.

Test plan

  • Existing plugin.spec.ts test suite unchanged behavior (pnpm test in core/)
  • New regression test passes: typeof proxy.then === 'undefined' + Promise.resolve(proxy) === proxy
  • Verified on web platform via core/'s Jest tests
  • Verified on iOS + Android via Capacitor sample app (no behavior change expected; proxy method-dispatch flow unchanged)

Affected versions

Bug verified in @capacitor/core@8.3.1, @capacitor/core@9.0.0-alpha.2, and current main. The proxy structure has been stable across these versions; the fix applies cleanly.

Backward compatibility

Zero behavior change for any code that uses the proxy for its intended purpose (await proxy.someMethod(args)). Eliminates a latent footgun for code that accidentally feeds the proxy through Promise resolution.

Related context

// https://github.com/facebook/react/issues/20030 comment at core/src/runtime.ts:167 already documents the precedent: framework-internal property access (React's $$typeof) needs explicit short-circuit in the proxy. Promise machinery's .then/.catch/.finally checks are architecturally equivalent — same problem class, same solution shape.

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.

Plugin proxy is unintentionally Thenable: get-trap default returns function wrapper for 'then' / 'catch' / 'finally'

1 participant