Architecture
This page describes how the BB Channel SDK works under the hood.
Split Bundle Design
The channel JS is split into three bundles, loaded in sequence:
channel-native.js (~1KB, loader)
→ native-bridge.js (~15KB, WebView ↔ RN communication)
→ channel.js (~600KB, full web channel app)
channel-native.js is the entry point injected into the WebView. It detects the native environment, sets up window.__bbNativeMode, and loads the other two bundles.
native-bridge.js implements the communication layer between WebView JavaScript and React Native. It serializes events as JSON messages and dispatches commands received from the native side.
channel.js is the full web channel application (React, routing, components, API layer). It is the same codebase used for web embeds, with native-mode guards that adapt behavior when running inside a WebView.
Why Split?
- Independent versioning. The bridge can be updated without rebuilding the channel, and vice versa.
- Caching. The large channel bundle is cached separately. Bridge-only fixes don't invalidate it.
- Loader flexibility. The loader can point to different channel/bridge versions or load bundled JS from the app binary instead of CDN.
WebView Bridge
Communication between the WebView and React Native uses two mechanisms:
WebView → React Native (Events)
The bridge calls window.ReactNativeWebView.postMessage(JSON.stringify(event)) for every event (media play, navigation, errors, etc.). The native BBChannel component receives these in the WebView's onMessage handler, deserializes them, and fires the corresponding callback props.
React Native → WebView (Commands)
The native side injects JavaScript via the WebView ref's injectJavaScript(). Commands are dispatched to handler functions registered on window.__bbChannel* globals:
window.__bbChannelNavigateTo(state)— navigate to a pagewindow.__bbChannelGoBack()— go back in the navigation stackwindow.__bbChannelSearch(query)— trigger searchwindow.__bbChannelGetPlayers()— list active players
Navigation: Custom History Stack
The channel uses a custom navigation history stack instead of the browser's history.pushState/history.back(). This is necessary because WebView history.back() is broken when loading inline HTML — it navigates back to about:blank instead of the previous channel page. This is a known limitation documented in react-native-webview issues #3229 and #2608.
The custom stack:
- Pushes entries on every navigation (detail page, overview, search).
- Pops on
goBack(), restoring the previous page state. - Fires
onCanGoBackChange(boolean)so the app can enable/disable back buttons. - Integrates with Android's hardware back button and iOS swipe-back gesture via the
BBChannelcomponent's built-in handler.
CDN Deployment
Built bundles are deployed to the Blue Billywig CDN:
https://cdn-{env}.bluebillywig.com/apps/channel/{version}/
├── channel-native.js
├── native-bridge.js
└── channel.js
Environments: dev, test, acc, prod. The SDK defaults to the latest version on the production CDN but can be overridden via the bundleUrl prop.
When the SDK is built with bundled JS (via bundle-channel.js script), the bundles are embedded in the app binary and loaded from disk instead of CDN — no network required for the initial load.
Native Mode Detection
The channel detects native mode by checking for window.__bbNativeMode (set by the loader). When active:
- CSS adapts. Removes overflow scrolling on the root element, adjusts safe area padding, hides browser-only UI.
- Player strategy changes. Instead of creating an iframe-based web player, the channel posts a
mediaPlayevent so the native side can create a native player (AVPlayer/ExoPlayer via@bluebillywig/react-native-bb-player). - Navigation uses the custom stack instead of hash-based routing.
- Touch handling defers to React Native gesture system for swipe-back navigation.
Zero Web Impact
All native-specific code is gated behind bridge/nativeMode checks:
if (window.__bbNativeMode) {
// native-only behavior
}
The web build of channel.js is completely unchanged. The __bbNativeMode flag is never set outside the WebView context, so none of the native code paths execute on web. Native-only source files live under src/native/ and are never imported by web entry points.