# Partner Native Bridge Protocol

The Trillboards Lite SDK includes a **Universal Native Bridge** that enables seamless
communication between the SDK (running in a WebView/iframe) and your native host
application. This allows you to receive ad events and send commands from Android,
iOS, React Native, Flutter, CTV, Electron, and other platforms.

## Overview

When embedding the Trillboards Lite SDK in a native application, you need a way to:

1. **Receive events** when ads start, complete, error, or have no fill
2. **Send commands** to show/hide ads, skip, or refresh

The Native Bridge handles this by:
- **Auto-detecting** standard bridge interfaces (Android, iOS, React Native, etc.)
- **Allowing custom bridge registration** for non-standard integrations
- **Using a standardized event protocol** across all platforms

## Quick Start (Zero Config)

For most platforms, the SDK auto-detects the native bridge. Just expose the
standard interface in your native code:

### Android WebView

```kotlin
// In your WebView setup
webView.addJavascriptInterface(object {
    @JavascriptInterface
    fun onEvent(payload: String) {
        val event = JSONObject(payload)
        when (event.getString("event")) {
            "ad_started" -> handleAdStarted(event.getJSONObject("data"))
            "ad_completed" -> handleAdCompleted(event.getJSONObject("data"))
            "ad_error" -> handleAdError(event.getJSONObject("data"))
            "no_ads" -> handleNoAds(event.getJSONObject("data"))
        }
    }
}, "TrillboardsBridge")
```

### iOS WKWebView

```swift
// In your WKWebView setup
let contentController = webView.configuration.userContentController
contentController.add(self, name: "trillboards")

// Implement WKScriptMessageHandler
extension YourViewController: WKScriptMessageHandler {
    func userContentController(_ controller: WKUserContentController,
                               didReceive message: WKScriptMessage) {
        guard message.name == "trillboards",
              let body = message.body as? [String: Any],
              let event = body["event"] as? String else { return }

        switch event {
        case "ad_started": handleAdStarted(body["data"] as? [String: Any])
        case "ad_completed": handleAdCompleted(body["data"] as? [String: Any])
        case "ad_error": handleAdError(body["data"] as? [String: Any])
        case "no_ads": handleNoAds(body["data"] as? [String: Any])
        default: break
        }
    }
}
```

### React Native WebView

```tsx
import { WebView } from 'react-native-webview';

function AdPlayer() {
  const handleMessage = (event: WebViewMessageEvent) => {
    const data = JSON.parse(event.nativeEvent.data);
    if (data.type !== 'trillboards') return;

    switch (data.event) {
      case 'ad_started':
        console.log('Ad started:', data.data);
        break;
      case 'ad_completed':
        console.log('Ad completed:', data.data);
        break;
      case 'ad_error':
        console.log('Ad error:', data.data);
        break;
      case 'no_ads':
        console.log('No ads available:', data.data);
        break;
    }
  };

  return (
    <WebView
      source={{ uri: 'https://screen.trillboards.com/partner-sdk/embed.html?device=YOUR_DEVICE_ID' }}
      onMessage={handleMessage}
      javaScriptEnabled
      mediaPlaybackRequiresUserAction={false}
    />
  );
}
```

### Flutter WebView

```dart
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:convert';

class AdPlayerWidget extends StatefulWidget {
  @override
  _AdPlayerWidgetState createState() => _AdPlayerWidgetState();
}

class _AdPlayerWidgetState extends State<AdPlayerWidget> {
  late WebViewController _controller;

  @override
  Widget build(BuildContext context) {
    return WebViewWidget(
      controller: WebViewController()
        ..setJavaScriptMode(JavaScriptMode.unrestricted)
        ..addJavaScriptChannel(
          'Flutter',
          onMessageReceived: (message) {
            final data = jsonDecode(message.message);
            if (data['type'] != 'trillboards') return;

            switch (data['event']) {
              case 'ad_started':
                print('Ad started: ${data['data']}');
                break;
              case 'ad_completed':
                print('Ad completed: ${data['data']}');
                break;
              case 'ad_error':
                print('Ad error: ${data['data']}');
                break;
              case 'no_ads':
                print('No ads: ${data['data']}');
                break;
            }
          },
        )
        ..loadRequest(Uri.parse(
          'https://screen.trillboards.com/partner-sdk/embed.html?device=YOUR_DEVICE_ID'
        )),
    );
  }
}
```

## Auto-Detection Matrix

The SDK automatically detects these interfaces (in order):

| Platform | Detection | Interface |
|----------|-----------|-----------|
| Android WebView | `window.TrillboardsBridge.onEvent` | `TrillboardsBridge.onEvent(jsonString)` |
| Android (alt) | `window.Android.onTrillboardsEvent` | `Android.onTrillboardsEvent(jsonString)` |
| iOS WKWebView | `window.webkit.messageHandlers.trillboards` | `trillboards.postMessage(object)` |
| React Native | `window.ReactNativeWebView.postMessage` | `ReactNativeWebView.postMessage(jsonString)` |
| Flutter | `window.Flutter.postMessage` | `Flutter.postMessage(jsonString)` |
| CTV (Tizen/webOS/Fire TV) | `window.__TRILL_CTV_BRIDGE__.postEvent` | `__TRILL_CTV_BRIDGE__.postEvent(jsonString)` |
| Electron | `window.electronAPI.trillboardsEvent` | `electronAPI.trillboardsEvent(object)` |
| Tauri | `window.__TAURI__.event` | Emits `trillboards-event` |
| Iframe embed | `window.parent !== window` | `parent.postMessage(object, '*')` |

## Custom Bridge Registration

For non-standard integrations, register a custom bridge:

```javascript
// In your WebView's JavaScript context
TrillboardsLite.registerBridge({
    // Required: how to send events to host
    send: function(event, data) {
        // Your custom transport
        MyCustomBridge.sendEvent(JSON.stringify({ event, data }));
    },

    // Optional: receive commands from host
    receive: function(callback) {
        // Set up listener for host commands
        MyCustomBridge.onCommand = callback;
    },

    // Optional: filter which events to send (reduces noise)
    events: ['ad_started', 'ad_completed', 'ad_error', 'no_ads'],

    // Optional: bridge name for debugging
    name: 'MyCustomBridge'
});
```

## Event Protocol

All events follow this standardized payload structure:

```typescript
interface TrillboardsEvent {
    type: 'trillboards';           // Message type identifier
    version: '1.0';                // Protocol version
    event: string;                 // Event name (see table below)
    data: object;                  // Event-specific data
    timestamp: number;             // Unix timestamp (ms)
    deviceId: string;              // Device identifier
    screenId?: string;             // Screen identifier (if linked)
    sessionId: string;             // Session identifier
}
```

### Events Reference

| Event | Description | Data |
|-------|-------------|------|
| `ready` | SDK initialized | `{ deviceId, screenId, adCount, waterfallMode }` |
| `ad_started` | Ad began playing | `{ type: 'programmatic'\|'image'\|'video', adId?, url? }` |
| `ad_completed` | Ad finished naturally | `{ type, reason: 'complete', completed: true }` |
| `ad_skipped` | User skipped ad | `{ type }` |
| `ad_ended` | Ad playback ended | `{ type?, reason? }` |
| `ad_error` | Ad failed | `{ type, error: { message, code? } }` |
| `no_ads` | No fill available | `{ reason: 'no_fill'\|'offline'\|'programmatic_unavailable' }` |
| `overlay_clicked` | User tapped CTA | `{}` |
| `ads_refreshed` | Ad list updated | `{ count }` |
| `online` | Network restored | `{}` |
| `offline` | Network lost | `{}` |
| `state_changed` | State response | Full state object (see `getState()`) |

## Sending Commands to SDK

Your native app can send commands to control the SDK.

### From Android

```kotlin
// Show an ad
webView.evaluateJavascript("TrillboardsLite.show()", null)

// Hide the player
webView.evaluateJavascript("TrillboardsLite.hide()", null)

// Skip current ad
webView.evaluateJavascript("TrillboardsLite.skipAd()", null)

// Refresh ad list
webView.evaluateJavascript("TrillboardsLite.refresh()", null)
```

### From iOS

```swift
// Show an ad
webView.evaluateJavaScript("TrillboardsLite.show()")

// Hide the player
webView.evaluateJavaScript("TrillboardsLite.hide()")
```

### From React Native

```tsx
const webViewRef = useRef<WebView>(null);

// Show an ad
webViewRef.current?.injectJavaScript('TrillboardsLite.show(); true;');

// Hide the player
webViewRef.current?.injectJavaScript('TrillboardsLite.hide(); true;');
```

### Via postMessage (Iframe)

```javascript
// From parent window to iframe
const iframe = document.getElementById('trillboards-iframe');

// Show an ad
iframe.contentWindow.postMessage({
    type: 'trillboards-command',
    action: 'show'
}, '*');

// Hide the player
iframe.contentWindow.postMessage({
    type: 'trillboards-command',
    action: 'hide'
}, '*');

// Get current state (response via state_changed event)
iframe.contentWindow.postMessage({
    type: 'trillboards-command',
    action: 'getState'
}, '*');

// Update configuration
iframe.contentWindow.postMessage({
    type: 'trillboards-command',
    action: 'configure',
    params: { waterfall: 'programmatic_only', volume: 0 }
}, '*');
```

### Command Reference

| Command | Description | Parameters |
|---------|-------------|------------|
| `show` | Request and play an ad | `{ immediate?: boolean }` |
| `hide` | Stop and hide player | `{ silent?: boolean }` |
| `skip` | Skip current ad | `{}` |
| `refresh` | Refresh ad list from API | `{}` |
| `getState` | Request current state | `{}` (responds with `state_changed`) |
| `configure` | Update settings | `{ waterfall?, volume? }` |

## Platform-Specific Guides

### CTV (Smart TVs)

For Tizen (Samsung), webOS (LG), Fire TV, and Android TV:

```javascript
// On your CTV app, expose this interface before loading the WebView
window.__TRILL_CTV_BRIDGE__ = {
    postEvent: function(jsonString) {
        // Parse and route to your CTV app's event system
        const event = JSON.parse(jsonString);
        TizenBridge.sendEvent(event.event, event.data);
    }
};
```

### Electron

```javascript
// In preload.js
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
    trillboardsEvent: (data) => {
        ipcRenderer.send('trillboards-event', data);
    }
});

// In main.js
ipcMain.on('trillboards-event', (event, data) => {
    console.log('Trillboards event:', data.event, data.data);
});
```

### Tauri

```rust
// In your Tauri app
use tauri::Manager;

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            let window = app.get_window("main").unwrap();
            window.listen("trillboards-event", |event| {
                println!("Trillboards event: {:?}", event.payload());
            });
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
```

### Capacitor/Cordova

```typescript
// Using @capawesome/capacitor-app-webview
import { AppWebview } from '@capawesome/capacitor-app-webview';

// Listen for messages
AppWebview.addListener('message', (event) => {
    const data = JSON.parse(event.message);
    if (data.type === 'trillboards') {
        console.log('Trillboards event:', data.event);
    }
});
```

## Event Filtering

To reduce noise, filter events at registration:

```javascript
TrillboardsLite.registerBridge({
    send: (event, data) => { /* ... */ },
    // Only receive these critical events
    events: ['ad_started', 'ad_completed', 'ad_error', 'no_ads']
});
```

## Best Practices

### 1. Handle Offline Gracefully

The SDK caches ads for offline playback. Listen for `online`/`offline` events:

```kotlin
"offline" -> {
    // Show offline indicator or pause ad requests
}
"online" -> {
    // Resume normal operation
}
```

### 2. Track Session State

Use the `sessionId` in events to correlate ads within a user session:

```kotlin
val sessionMetrics = mutableMapOf<String, MutableList<String>>()

fun handleEvent(event: JSONObject) {
    val sessionId = event.getString("sessionId")
    val eventName = event.getString("event")
    sessionMetrics.getOrPut(sessionId) { mutableListOf() }.add(eventName)
}
```

### 3. Implement Retry Logic

When `no_ads` occurs, the SDK automatically retries in `programmatic_only` mode.
For manual control:

```kotlin
"no_ads" -> {
    // SDK handles retry automatically, but you can track
    Log.d("Trillboards", "No fill - SDK will retry")
}
```

### 4. Clean Up on Destroy

```kotlin
override fun onDestroy() {
    webView.evaluateJavascript("TrillboardsLite.destroy()", null)
    super.onDestroy()
}
```

## Troubleshooting

### Events Not Received

1. **Check interface name**: Ensure your bridge uses the exact name the SDK expects
2. **Check timing**: Register your interface before the WebView loads the SDK
3. **Check console**: Look for `[Trillboards] Bridge auto-detected:` log
4. **Use custom bridge**: If auto-detection fails, register explicitly

### Commands Not Working

1. **Check WebView is loaded**: Wait for page load before sending commands
2. **Check JavaScript enabled**: Ensure `javaScriptEnabled = true`
3. **Check console errors**: Look for JavaScript errors in WebView

### Debugging

Enable verbose logging:

```javascript
// In WebView console
console.log('[Trillboards] Bridge detected:', window.TrillboardsBridge ? 'yes' : 'no');
console.log('[Trillboards] State:', TrillboardsLite.getState());
```

## FAQ

### Is `/programmatic-event` API for native communication?

**No.** The `/device/{deviceId}/programmatic-event` endpoint is a **server-side
analytics endpoint**. It records events for Trillboards' internal analytics and
triggers webhooks. It does NOT push events to your native app.

For native app event delivery, use the **JavaScript Native Bridge** described
in this document.

### Can I use both webhooks and native bridge?

**Yes.** They serve different purposes:
- **Native Bridge**: Real-time events to your native app UI
- **Webhooks**: Server-side event processing, analytics, billing integration

### What if my platform isn't listed?

Use the custom bridge registration:

```javascript
TrillboardsLite.registerBridge({
    send: (event, data) => {
        // Your custom transport mechanism
    }
});
```

### How do I test the bridge?

1. Load the SDK in your WebView
2. Open developer console
3. Trigger an ad: `TrillboardsLite.show()`
4. Check your native handler receives events

## Related Documentation

- [Lite SDK Overview](partner-lite-sdk.md)
- [Local Testing](partner-lite-sdk-local-testing.md)
- [Partner CMS API](partner-cms-api.md)
- [Programmatic Settings](partner-programmatic-settings.md)
