fix(runtime): plugin proxy must not be unintentionally Thenable#8473
Open
playopsstudio wants to merge 1 commit into
Open
fix(runtime): plugin proxy must not be unintentionally Thenable#8473playopsstudio wants to merge 1 commit into
playopsstudio wants to merge 1 commit into
Conversation
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.
fix(runtime): plugin proxy must not be unintentionally Thenable
Summary
Adds
case 'then',case 'catch', andcase 'finally'to the plugin-proxyget-trap switch incore/src/runtime.ts, returningundefined. Aligns with the existing$$typeofshort-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
.thenas a Thenable. The proxy's get-trap returns a method-wrapper function for any property name not in the switch — including.then. SoPromise.resolve(proxy)invokesproxy.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 ofPromise<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 incore/src/tests/plugin.spec.ts.Test plan
plugin.spec.tstest suite unchanged behavior (pnpm testincore/)typeof proxy.then === 'undefined'+Promise.resolve(proxy) === proxycore/'s Jest testsAffected versions
Bug verified in
@capacitor/core@8.3.1,@capacitor/core@9.0.0-alpha.2, and currentmain. 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/20030comment atcore/src/runtime.ts:167already documents the precedent: framework-internal property access (React's$$typeof) needs explicit short-circuit in the proxy. Promise machinery's.then/.catch/.finallychecks are architecturally equivalent — same problem class, same solution shape.