Summary
It is currently impossible to expose the Meta Horizon Platform SDK to a PWA's
JavaScript context from a Bubblewrap-packaged app running on Meta Quest, because
com.oculus.browser does not implement CustomTabsService.
Device & environment
- Device: Meta Quest 3
- HorizonOS: Android 14
- Bubblewrap: meta-quest fork (latest)
com.oculus.browser: system browser
What we tried
We investigated every available mechanism to bridge the Platform SDK
(android-platform-sdk:77) to JavaScript running inside the TWA:
1. postMessage via CustomTabsSession — blocked
The TWA postMessage API (requestPostMessageChannel) requires CustomTabsService,
which is not implemented by com.oculus.browser:
W ActivityManager: Unable to start service Intent {
act=android.support.customtabs.action.CustomTabsService
pkg=com.oculus.browser
} U=0: not found
bindCustomTabsService() returns null, the channel never opens,
and no JS can be injected into the page.
2. JavascriptInterface — blocked
TWAs delegate rendering to the system browser. There is no WebView instance
in the Android view hierarchy to call addJavascriptInterface() on.
3. ExtraCommandHandler / TrustedWebActivityCallbackRemote — JS side not exposed
DelegationService.onExtraCommand() works on the native side, but
com.oculus.browser does not expose any corresponding JS API
(navigator.sendExtraCommand is undefined, no window.trustedWebActivity
or similar object is injected into the page).
4. WebViewFallbackActivity — never triggered
Quest's TWA implementation does not fall back to WebViewFallbackActivity
even when fallbackType = "webview" is set in the manifest. The app always
launches through LauncherActivity using the system browser.
Impact
This blocks the entire Platform SDK surface from being accessible in PWAs:
Users.getLoggedInUser() — user identity / alias
Leaderboards.getEntries() / writeEntry() — leaderboards
Achievements.unlock() — achievements
IAP.getViewerPurchases() — in-app purchases
These are core social and monetisation features that native apps access trivially.
PWAs on Quest have no equivalent path today.
What would fix this
Any one of the following would unblock the JS bridge:
-
com.oculus.browser implements CustomTabsService — the postMessage
channel would open and the bridge would work immediately with no changes
to Bubblewrap.
-
An official JS API injected by the browser — similar to how Chrome injects
APIs in TWA contexts, Quest's browser could expose a window.HorizonPlatform
or navigator.sendExtraCommand surface backed by the Platform SDK.
-
A documented WebView-based packaging mode — an officially supported
alternative to TWA for Quest that gives the app direct WebView access,
while remaining Store-compliant.
Question for the Meta browser / Bubblewrap team
Is support for CustomTabsService in com.oculus.browser on the roadmap?
Is there an alternative approach we may have missed?
We are happy to contribute a Bubblewrap implementation the moment a supported
mechanism is available.
Summary
It is currently impossible to expose the Meta Horizon Platform SDK to a PWA's
JavaScript context from a Bubblewrap-packaged app running on Meta Quest, because
com.oculus.browserdoes not implementCustomTabsService.Device & environment
com.oculus.browser: system browserWhat we tried
We investigated every available mechanism to bridge the Platform SDK
(
android-platform-sdk:77) to JavaScript running inside the TWA:1. postMessage via CustomTabsSession — blocked
The TWA postMessage API (
requestPostMessageChannel) requiresCustomTabsService,which is not implemented by
com.oculus.browser:bindCustomTabsService()returnsnull, the channel never opens,and no JS can be injected into the page.
2. JavascriptInterface — blocked
TWAs delegate rendering to the system browser. There is no
WebViewinstancein the Android view hierarchy to call
addJavascriptInterface()on.3. ExtraCommandHandler / TrustedWebActivityCallbackRemote — JS side not exposed
DelegationService.onExtraCommand()works on the native side, butcom.oculus.browserdoes not expose any corresponding JS API(
navigator.sendExtraCommandisundefined, nowindow.trustedWebActivityor similar object is injected into the page).
4. WebViewFallbackActivity — never triggered
Quest's TWA implementation does not fall back to
WebViewFallbackActivityeven when
fallbackType = "webview"is set in the manifest. The app alwayslaunches through
LauncherActivityusing the system browser.Impact
This blocks the entire Platform SDK surface from being accessible in PWAs:
Users.getLoggedInUser()— user identity / aliasLeaderboards.getEntries()/writeEntry()— leaderboardsAchievements.unlock()— achievementsIAP.getViewerPurchases()— in-app purchasesThese are core social and monetisation features that native apps access trivially.
PWAs on Quest have no equivalent path today.
What would fix this
Any one of the following would unblock the JS bridge:
com.oculus.browserimplementsCustomTabsService— the postMessagechannel would open and the bridge would work immediately with no changes
to Bubblewrap.
An official JS API injected by the browser — similar to how Chrome injects
APIs in TWA contexts, Quest's browser could expose a
window.HorizonPlatformor
navigator.sendExtraCommandsurface backed by the Platform SDK.A documented
WebView-based packaging mode — an officially supportedalternative to TWA for Quest that gives the app direct
WebViewaccess,while remaining Store-compliant.
Question for the Meta browser / Bubblewrap team
Is support for
CustomTabsServiceincom.oculus.browseron the roadmap?Is there an alternative approach we may have missed?
We are happy to contribute a Bubblewrap implementation the moment a supported
mechanism is available.