Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ export const createCapacitor = (win: WindowCapacitor): CapacitorInstance => {
return pluginHeader ? addListenerNative : addListener;
case 'removeListener':
return removeListener;
// Promise-machinery short-circuit. See #8472 for failure modes.
case 'then':
case 'catch':
case 'finally':
return undefined;
default:
return createPluginMethodWrapper(prop);
}
Expand Down
17 changes: 17 additions & 0 deletions core/src/tests/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,23 @@ describe('plugin', () => {
],
});
};

it('plugin proxy must not be a Thenable (Promise.resolve chaining short-circuit)', async () => {
// Web platform; no native bridge headers. registerPlugin returns a Proxy
// that wraps method dispatch. The proxy MUST NOT be unintentionally Thenable
// — otherwise Promise.resolve(proxy) and `await proxy` invoke proxy.then(...)
// and dispatch a bogus "then" method to the native bridge (or web stub).
// See GH issue #8472 for the production failure modes that motivated this regression test.
cap = initCapacitorGlobal(win);
const Awesome = cap.registerPlugin('Awesome');

expect(typeof (Awesome as unknown as { then?: unknown }).then).toBe('undefined');
expect(typeof (Awesome as unknown as { catch?: unknown }).catch).toBe('undefined');
expect(typeof (Awesome as unknown as { finally?: unknown }).finally).toBe('undefined');

const resolved = await Promise.resolve(Awesome);
expect(resolved).toBe(Awesome);
});
});

interface AwesomePlugin extends Plugin {
Expand Down