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
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The **Taboola React Native Plugin 4.x** introduces full support for the **React
## Sample App Features

This sample app showcases:
- Multiple integration patterns (Feed, Widget, Classic Page)
- Multiple integration patterns (Feed, Widget, Classic Page, Web Integration)
- Custom click handling and organic content management
- Dark mode configuration
- Memory management best practices
Expand Down Expand Up @@ -100,6 +100,14 @@ The app includes several screens demonstrating different integration patterns:
- Extra properties and advanced options
- Dynamic placement configuration

### 🌐 **Web Integration** (`WebIntegrationScreen.tsx`)
- Publisher-owned `react-native-webview` with the Taboola bridge attached via `TBLWebviewWrapper`
- Three-step usage: `Taboola.getWebPage()` → wrap the WebView → load the real content page inside `onWebviewRegistered`
- Handles the iOS ordering caveat (start blank, then navigate)
- Sample Taboola HTML page lives in `src/screens/webIntegration/taboolaPageHtml.ts` — in a real integration, this HTML comes from the publisher's CMS
- Requires the New Architecture (TurboModules + Fabric) and `react-native-webview`
- Full reference: [Web Integration (React Native Plugin 4.x)](https://tbla.atlassian.net/wiki/spaces/MOBILE/pages/813269024/Web+Integration+React+Native+Plugin+4.x)

## Key Files to Examine

| File | Purpose |
Expand All @@ -108,6 +116,8 @@ The app includes several screens demonstrating different integration patterns:
| `src/screens/TBLClassicPageScreen.tsx` | Widget integration within content |
| `src/screens/ShouldHandleOrganicClicksScreen.tsx` | Custom click handling patterns |
| `src/screens/GlobalSettingsScreen.tsx` | Configuration and settings examples |
| `src/screens/WebIntegrationScreen.tsx` | Web Integration — attach Taboola bridge to a publisher-owned WebView |
| `src/screens/webIntegration/taboolaPageHtml.ts` | Sample publisher HTML page with the Taboola tag |
| `src/App.tsx` | Application entry point and Taboola initialization |

## Support
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@react-native-picker/picker": "^2.11.0",
"@react-navigation/drawer": "^7.3.12",
"@react-navigation/native": "^7.1.9",
//TODO: Begore merging bump to '4.0.10' once it is officially released — WebIntegrationScreen depends on APIs (TBLWebviewWrapper, Taboola.getWebPage) shipped in 4.0.10."
"@taboola/react-native-plugin-4x": "^4.0.4",
"react": "19.0.0",
"react-native": "0.79.2",
Expand Down
6 changes: 6 additions & 0 deletions src/navigation/AppNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ShouldHandleOrganicClicksScreen from '../screens/ShouldHandleOrganicClick
import GlobalSettingsScreen from '../screens/GlobalSettingsScreen';
import { useIsFocused } from '@react-navigation/native';
import DarkModeScreen from "../screens/DarkModeScreen.tsx";
import WebIntegrationScreen from '../screens/WebIntegrationScreen';

const Drawer = createDrawerNavigator();

Expand Down Expand Up @@ -73,6 +74,11 @@ const AppNavigator = () => {
component={DarkModeScreen}
options={{ title: SCREEN_TITLES.DARK_MODE }}
/>
<Drawer.Screen
name={SCREENS.WEB_INTEGRATION}
component={WebIntegrationScreen}
options={{ title: SCREEN_TITLES.WEB_INTEGRATION }}
/>
</Drawer.Navigator>
);
};
Expand Down
144 changes: 144 additions & 0 deletions src/screens/WebIntegrationScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import {
useCallback,
useEffect,
useMemo,
useState,
type FC,
type ForwardRefExoticComponent,
type RefAttributes,
} from 'react';
import { Alert, StyleSheet, View } from 'react-native';
import WebViewImpl, { type WebViewProps } from 'react-native-webview';
import {
Taboola,
TBLWebviewWrapper,
TBLWebUnitController,
type TBLWebListener,
} from '@taboola/react-native-plugin-4x';
import { COLORS, PublisherName, PLACEMENT_PARAMS } from '../utils/constants';
import {
buildTaboolaPageHtml,
TABOOLA_CONTENT_BASE_URL,
} from './webIntegration/taboolaPageHtml';

// Type-only workaround, not part of the Taboola integration. Under React 19
// the type exported by `react-native-webview` collapses to `never` in JSX
// position; this alias restores a usable ref-forwarding component type.
// Remove once react-native-webview ships React 19–compatible types.
const WebView = WebViewImpl as unknown as ForwardRefExoticComponent<
WebViewProps & RefAttributes<WebViewImpl>
>;

const noop = () => {};

type WebViewSource = { uri: string } | { html: string; baseUrl?: string };

// iOS caveat: the Taboola bridge is a WKScriptMessageHandler that WebKit only
// exposes to a page whose load started AFTER registration. So the WebView
// must start on a throwaway page and navigate to the real content only
// inside `onWebviewRegistered`. `{ uri: 'about:blank' }` hits react-native-
// webview's file-URL path on iOS and throws — use an empty HTML doc instead.
const BLANK_PAGE_SOURCE: WebViewSource = { html: '<html></html>' };

const PUBLISHER_ID = PublisherName.SDK_TESTER_RND;
const CONTENT_HTML = buildTaboolaPageHtml({
publisherId: PUBLISHER_ID,
topPlacement: PLACEMENT_PARAMS.DARK_MODE_1X2_WIDGET.placement,
topMode: PLACEMENT_PARAMS.DARK_MODE_1X2_WIDGET.mode,
bottomPlacement: PLACEMENT_PARAMS.FEED_WITHOUT_VIDEO.placement,
bottomMode: PLACEMENT_PARAMS.FEED_WITHOUT_VIDEO.mode,
});

/**
* Web Integration demo — the publisher owns the WebView; the Taboola plugin
* only attaches its native↔JS bridge onto it.
*
* The three numbered steps below mirror the official usage snippet in the
* Confluence doc "Web Integration (React Native Plugin 4.x)".
*/
const WebIntegrationScreen: FC = () => {
// 1. Create a web page handle and remove it on unmount.
const [tblWebPage] = useState(() => Taboola.getWebPage());
useEffect(
() => () => {
Taboola.removeWebPage(tblWebPage.pageId);
},
[tblWebPage]
);

// Start blank; swap to the content page only after registration (see the
// iOS caveat on BLANK_PAGE_SOURCE above).
const [source, setSource] = useState<WebViewSource>(BLANK_PAGE_SOURCE);

const tblWebListener = useMemo<TBLWebListener>(
() => ({
onRenderSuccessful: (placement, height) => {
console.log(
`[WebIntegration] onRenderSuccessful placement="${placement}" height=${height}`
);
},
onRenderFailed: (placement, error) => {
console.log(
`[WebIntegration] onRenderFailed placement="${placement}" error=${error}`
);
},
}),
[]
);

// 3. Load the real content page once the bridge is registered.
const handleWebviewRegistered = useCallback(
(_controller: TBLWebUnitController) => {
setSource({ html: CONTENT_HTML, baseUrl: TABOOLA_CONTENT_BASE_URL });
},
[]
);

const handleRegistrationFailed = useCallback(
({ code, message }: { code: string; message: string }) => {
Alert.alert(`Registration failed: ${code}`, message);
},
[]
);

return (
<View style={styles.container}>
{/* 2. Wrap the publisher WebView. The wrapper attaches the Taboola
bridge; the WebView itself is fully publisher-owned. */}
<TBLWebviewWrapper
tblWebPage={tblWebPage}
tblWebListener={tblWebListener}
onWebviewRegistered={handleWebviewRegistered}
onWebviewRegistrationFailed={handleRegistrationFailed}
>
<WebView
source={source}
originWhitelist={['*']}
javaScriptEnabled={true}
// iOS caveat: react-native-webview only enables message handling
// (which the Taboola bridge needs on iOS) when `onMessage` is set.
// The plugin turns it on during registration; this no-op is a
// belt-and-suspenders fallback for older react-native-webview
// versions. Android is unaffected.
onMessage={noop}
// iOS scroll-feel opt-in: react-native-webview leaves WKWebView's
// deceleration unset, which iOS treats as `fast` — a hard flick on
// a long Taboola feed brakes after only ~3-4 cards. `0.998` is the
// numeric equivalent of `"normal"` (UIScrollViewDecelerationRateNormal).
// On React Native < 0.81 use the Float form (`{0.998}`); on RN >= 0.81
// the string form (`"normal"`) is also accepted. No-op on Android.
decelerationRate={0.998}
webviewDebuggingEnabled={true}
style={styles.webView}
/>
</TBLWebviewWrapper>
</View>
);
};

const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: COLORS.BACKGROUND },
webView: { flex: 1 },
});

export default WebIntegrationScreen;
56 changes: 56 additions & 0 deletions src/screens/webIntegration/taboolaPageHtml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Sample HTML page with a standard Taboola tag. In a real integration this
// content comes from the publisher's CMS — nothing here is React-Native or
// SDK specific. It is included in the sample only so the screen has a real
// page to load.
//
// If you want to see what a minimal Taboola page looks like, this is it:
// two containers (`taboola-rn-top`, `taboola-rn-bottom`), the mobile-loader
// script, and one `_taboola.push({...})` per placement.
export const buildTaboolaPageHtml = (params: {
publisherId: string;
topPlacement: string;
topMode: string;
bottomPlacement: string;
bottomMode: string;
}): string => `<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no" />
<script type="text/javascript">
window._taboola = window._taboola || [];
_taboola.push({ article: 'auto', url: '' });
!function (e, f, u, i) {
if (!document.getElementById(i)) {
e.async = 1; e.src = u; e.id = i;
f.parentNode.insertBefore(e, f);
}
}(document.createElement('script'),
document.getElementsByTagName('script')[0],
'https://cdn.taboola.com/libtrc/${params.publisherId}/mobile-loader.js',
'tb-mobile-loader-script');
</script>
</head>
<body>
<div id="taboola-rn-top"></div>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla bibendum
mauris eget odio fermentum, non elementum lectus dapibus.</p>
<p>In aliquam arcu eget nisl imperdiet finibus. Nunc pharetra sapien felis,
vitae aliquam lorem bibendum in. Donec lacinia blandit tellus quis rutrum.</p>
<div id="taboola-rn-bottom"></div>
<script type="text/javascript">
window._taboola = window._taboola || [];
_taboola.push({ mode: '${params.topMode}', container: 'taboola-rn-top', placement: '${params.topPlacement}', target_type: 'mix' });
_taboola.push({ mode: '${params.bottomMode}', container: 'taboola-rn-bottom', placement: '${params.bottomPlacement}', target_type: 'mix' });
_taboola['mobile'] = window._taboola['mobile'] || [];
_taboola['mobile'].push({
lazyFetch: false,
shouldWaitForSdkConfig: false,
allow_sdkless_load: false,
taboola_view_id: new Date().getTime(),
publisher: '${params.publisherId}'
});
_taboola.push({ flush: true });
</script>
</body>
</html>`;

export const TABOOLA_CONTENT_BASE_URL = 'https://cdn.taboola.com/mobile-sdk/init/';
2 changes: 2 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export const SCREENS = {
SHOULD_HANDLE_ORGANIC_CLICKS_SCREEN: 'ShouldHandleOrganicClicksScreen',
GLOBAL_SETTINGS: 'GlobalSettings',
DARK_MODE: 'Dark Mode',
WEB_INTEGRATION: 'WebIntegration',
};

export const SCREEN_TITLES = {
Expand All @@ -84,6 +85,7 @@ export const SCREEN_TITLES = {
SHOULD_HANDLE_ORGANIC_CLICKS_SCREEN: 'Should Handle OC Screen',
GLOBAL_SETTINGS: 'Global Settings Screen',
DARK_MODE: 'Dark Mode Screen',
WEB_INTEGRATION: 'Web Integration Screen',
};

export const PLACEMENT_PARAMS = {
Expand Down