diff --git a/README.md b/README.md index 7b32dc8..53571cd 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 | @@ -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 diff --git a/package.json b/package.json index 3bede0a..559ecb4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index db61fb9..b70b490 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -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(); @@ -73,6 +74,11 @@ const AppNavigator = () => { component={DarkModeScreen} options={{ title: SCREEN_TITLES.DARK_MODE }} /> + ); }; diff --git a/src/screens/WebIntegrationScreen.tsx b/src/screens/WebIntegrationScreen.tsx new file mode 100644 index 0000000..a4004c1 --- /dev/null +++ b/src/screens/WebIntegrationScreen.tsx @@ -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 +>; + +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: '' }; + +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(BLANK_PAGE_SOURCE); + + const tblWebListener = useMemo( + () => ({ + 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 ( + + {/* 2. Wrap the publisher WebView. The wrapper attaches the Taboola + bridge; the WebView itself is fully publisher-owned. */} + + = 0.81 + // the string form (`"normal"`) is also accepted. No-op on Android. + decelerationRate={0.998} + webviewDebuggingEnabled={true} + style={styles.webView} + /> + + + ); +}; + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.BACKGROUND }, + webView: { flex: 1 }, +}); + +export default WebIntegrationScreen; diff --git a/src/screens/webIntegration/taboolaPageHtml.ts b/src/screens/webIntegration/taboolaPageHtml.ts new file mode 100644 index 0000000..f74381f --- /dev/null +++ b/src/screens/webIntegration/taboolaPageHtml.ts @@ -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 => ` + + + + + +
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla bibendum + mauris eget odio fermentum, non elementum lectus dapibus.

+

In aliquam arcu eget nisl imperdiet finibus. Nunc pharetra sapien felis, + vitae aliquam lorem bibendum in. Donec lacinia blandit tellus quis rutrum.

+
+ + +`; + +export const TABOOLA_CONTENT_BASE_URL = 'https://cdn.taboola.com/mobile-sdk/init/'; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index bb68c1a..69d594d 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -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 = { @@ -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 = {