# Hybrid App

## 1. SDK 설치

### 1.1 Android

{% content-ref url="/pages/07ujY2ZLJDcbzJ7a5WW6" %}
[기본 설정](/ssp-sdk/sdk/android/undefined.md)
{% endcontent-ref %}

* 위 링크에 안내되어 있는 SDK Gradle 설치 및 AndroidManaifest.xml 설정을 완료해 줍니다.
* AdPopcornSSP v3.9.0 버전부터 지원합니다.

### 1.2 iOS

{% content-ref url="/pages/fMDRjZmIzDTLySHdYr2S" %}
[기본 설정](/ssp-sdk/sdk/ios/ios/undefined.md)
{% endcontent-ref %}

* 위 링크에 안내되어 있는 SDK 설치 및 IDFA 설정을 완료해 줍니다.
* iOS v2.10.7 버전부터 지원합니다.

## 2. 미디에이션 설정

애드팝콘에서는 2가지 형태의 미디에이션을 지원합니다. 하나의 방식을 선택한 뒤, OS 환경에 맞게 연동을 진행해 주세요.

* 애드팝콘 미디에이션

{% content-ref url="/pages/XOGmPFQT7s0MbvOgKf5n" %}
[AP 미디에이션(Android)](/ssp-sdk/undefined-1/ap/ap-android.md)
{% endcontent-ref %}

{% content-ref url="/pages/IP2R7MXm9WlEBdvJxfnZ" %}
[AP 미디에이션(iOS)](/ssp-sdk/undefined-1/ap/ap-ios.md)
{% endcontent-ref %}

* Max 미디에이션(Beta) : 사용하고자 할 경우 사업팀(<pm@adpopcorn.com>)에 문의 하세요

{% content-ref url="/pages/esnyF96ixFtxH0IuGWzx" %}
[MAX 미디에이션(Beta, Android)](/ssp-sdk/undefined-1/max-beta/max-beta-android.md)
{% endcontent-ref %}

{% content-ref url="/pages/inI4X4hiPeJ8WPpyw8Zg" %}
[MAX 미디에이션(Beta, iOS)](/ssp-sdk/undefined-1/max-beta/max-beta-ios.md)
{% endcontent-ref %}

## 3. 네이티브 웹뷰 연동

### 3.1 WebView 내 Javascript interface 설정

WebView 내 Javascript 설정을 활성화 및 AdPopcornSSPJsBridge 관련 세팅을 진행합니다.

반드시, name은 AdPopcornSSPJsBridge로 선언해 주어야 웹(HTML)과 통신이 이루어집니다.

{% tabs %}
{% tab title="Android(Java)" %}

```java
private WebView hybridWebView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_hybrid);

    hybridWebView = (WebView) findViewById(R.id.hybrid_webview);

    hybridWebView.getSettings().setJavaScriptEnabled(true);
    hybridWebView.getSettings().setDomStorageEnabled(true);	
    hybridWebView.addJavascriptInterface(new AdPopcornSSPJsBridge(this, hybridWebView), "AdPopcornSSPJsBridge");							
}
```

{% endtab %}

{% tab title="Android(Kotlin)" %}

```kotlin
private lateinit var hybridWebView: WebView

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_hybrid)
    
    hybridWebView = findViewById<WebView>(R.id.hybrid_webview)

    hybridWebView.apply {
        settings.javaScriptEnabled = true
        settings.domStorageEnabled = true
    
        addJavascriptInterface(
            AdPopcornSSPJsBridge(this@HybridActivity, this), 
            "AdPopcornSSPJsBridge"
        )
    }
}
```

{% endtab %}

{% tab title="iOS(Objective-C)" %}

```objectivec
#import "AdPopcornSSPWKScriptMessageHandler.h"

@interface AdPopcornRewardController() <WKNavigationDelegate, WKUIDelegate>
{

}

@implementation AdPopcornRewardController

- (void)viewDidLoad {
		webViewConfiguration = [[WKWebViewConfiguration alloc] init];
    wkContentController = [[WKUserContentController alloc] init];

    AdPopcornSSPWKScriptMessageHandler *scriptMessageHandler = [[AdPopcornSSPWKScriptMessageHandler alloc] init]; 
    [wkContentController addScriptMessageHandler:scriptMessageHandler name:@"AdPopcornSSPJsBridge"];
    [webViewConfiguration setUserContentController:wkContentController];
    
    webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) configuration:webViewConfiguration];
    // scriptMessageHandler에 webView와 vc설정 (필수)
    scriptMessageHandler.webView = webView;
    scriptMessageHandler.viewController = self;

    webView.navigationDelegate = self;
    webView.uiDelegate = self;
}
```

{% endtab %}

{% tab title="iOS(Swift)" %}

```swift
import UIKit
import WebKit

class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate{
    
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
                
        let webConfiguration = WKWebViewConfiguration()        
        let contentController = WKUserContentController()
        
        let scriptMessageHandler = AdPopcornSSPWKScriptMessageHandler()
        scriptMessageHandler.viewController = self
        contentController.add(scriptMessageHandler, name: "AdPopcornSSPJsBridge")
        webConfiguration.userContentController = contentController

        // WKWebView 초기화
        webView = WKWebView(frame: .zero, configuration: webConfiguration)

	// scriptMessageHandler에 webView와 vc설정 (필수)
        scriptMessageHandler.webView = webView
	scriptMessageHandler.viewController = self
        
        webView.navigationDelegate = self // 네비게이션 델리게이트 설정
        webView.uiDelegate = self // 네비게이션 델리게이트 설정        
    }
}
```

{% endtab %}
{% endtabs %}

## 4. 웹 Javacript API 연동

### 4.1 기본 연동

#### 4.1.1 초기화

* SDK 초기화 시 사용되는 API 입니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

<pre class="language-javascript"><code class="lang-javascript">if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
<strong>    window.AdPopcornSSPJsBridge.init();    
</strong>} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    } 
    window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
       action: "init"
    });
}
</code></pre>

{% endtab %}
{% endtabs %}

#### 4.1.2 해제 API(Only Android)

* SDK 해제 시 사용되는 API 입니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.destroy();    
} 
else if (ios) {
    // Not Supported
}
```

{% endtab %}
{% endtabs %}

### 4.2 전면 광고

#### 4.2.1 전면 광고 요청

* 전면 형태의 광고 요청이 필요할 때 사용되는 API 입니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.loadInterstitial(appKey, placementID);
} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    } 
  window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
    action: "loadInterstitial",
    appKey: appKey,
    placementID: placementID
  });
} 
```

{% endtab %}
{% endtabs %}

* appKey : 앱키
* placementID: 지면키

#### 4.2.2 전면 광고 노출

* 전면 광고 노출 시 사용되는 API 입니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.showInterstitial(appKey, placementID);
} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    } 
    window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
        action: "showInterstitial",
        appKey: appKey,
        placementID: placementID
    });

} 
```

{% endtab %}
{% endtabs %}

* appKey : 앱키
* placementID: 지면키

#### 4.2.3 전면 광고 이벤트

* 전면 광고 관련 이벤트를 받고자 할 때 사용됩니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```html
<body>
  <!-- ... -->
  <script>
    const handleNativeEvent = (e: CustomEvent) => {
      const eventData = e.detail.data;
      console.log(e.detail.data);
      // {
      //   EventName: `OnInterstitialLoaded`,
      //   Data:`{"placementId":"5jb921hjk4j2k4b"}`,
      // }
      // Android
      if (eventData && typeof eventData === 'object' && eventData.EventName) {
            const eventName = eventData.EventName;
            const dataString = eventData.Data;

            try {
                // 'Data' 속성에 담긴 JSON 문자열을 파싱
                const parsedData = JSON.parse(dataString);
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + JSON.stringify(parsedData, null, 2);
            } catch (error) {
                // 'Data'가 JSON 형식이 아닌 경우
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + `Raw Data: ${dataString}`;
            }
        }
    };

    window.addEventListener('NativeEvent', handleNativeEvent);
  </script>
</body>
```

{% endtab %}
{% endtabs %}

* **(전면) 지원되는 이벤트**

{% tabs %}
{% tab title="Android" %}

| Event                                                                          | 설명          |
| ------------------------------------------------------------------------------ | ----------- |
| <p><strong>OnInterstitialLoaded</strong><br>(- placementId)</p>                | 전면 광고 로드 성공 |
| <p><strong>OnInterstitialLoadFailed</strong><br>(- placementId, errorCode)</p> | 전면 광고 로드 실패 |
| <p><strong>OnInterstitialOpened</strong><br>(- placementId)</p>                | 전면 광고 노출 성공 |
| <p><strong>OnInterstitialOpenFailed</strong><br>(- placementId, errorCode)</p> | 전면 광고 노출 실패 |
| <p><strong>OnInterstitialClosed</strong><br>(- placementId)</p>                | 전면 광고 닫기    |
| <p><strong>OnInterstitialClicked</strong><br>(- placementId)</p>               | 전면 광고 클릭    |
| {% endtab %}                                                                   |             |

{% tab title="iOS" %}

| Event                                                                        | 설명          |
| ---------------------------------------------------------------------------- | ----------- |
| <p><strong>OnInterstitialLoadSuccess</strong><br>(- placementId)</p>         | 전면 광고 로드 성공 |
| <p><strong>OnInterstitialLoadFail</strong><br>(- placementId, errorCode)</p> | 전면 광고 로드 실패 |
| <p><strong>OnInterstitialShowSuccess</strong><br>(- placementId)</p>         | 전면 광고 노출 성공 |
| <p><strong>OnInterstitialShowFail</strong><br>(- placementId, errorCode)</p> | 전면 광고 노출 실패 |
| <p><strong>OnInterstitialClosed</strong><br>(- placementId)</p>              | 전면 광고 닫기    |
| <p><strong>OnInterstitialClicked</strong><br>(- placementId)</p>             | 전면 광고 클릭    |
| {% endtab %}                                                                 |             |
| {% endtabs %}                                                                |             |

### 4.3 전면 비디오 광고

#### 4.3.1 전면 비디오광고 요청

* 전면 형태의 비디오광고 요청이 필요할 때 사용되는 API 입니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.loadInterstitialVideo(appKey, placementID);
} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    }
    window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
        action: "loadInterstitialVideo",
        appKey: appKey,
        placementID: placementID
    });
} 
```

{% endtab %}
{% endtabs %}

* appKey : 앱키
* placementID: 지면키

#### 4.3.2 전면 비디오광고 노출

* 전면 비디오광고 노출 시 사용되는 API 입니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.showInterstitialVideo(appKey, placementID);
} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    }
    window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
        action: "showInterstitialVideo",
        appKey: appKey,
        placementID: placementID
    });
} 
```

{% endtab %}
{% endtabs %}

* appKey : 앱키
* placementID: 지면키

#### 4.3.3 전면 비디오 광고 이벤트

* 전면 비디오광고 관련 이벤트를 받고자 할 때 사용됩니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```html
<body>
  <!-- ... -->
  <script>
    const handleNativeEvent = (e: CustomEvent) => {
      const eventData = e.detail.data;
      console.log(e.detail.data);
      // {
      //   EventName: `OnInterstitialLoaded`,
      //   Data:`{"placementId":"5jb921hjk4j2k4b"}`,
      // }
      // Android
      if (eventData && typeof eventData === 'object' && eventData.EventName) {
            const eventName = eventData.EventName;
            const dataString = eventData.Data;

            try {
                // 'Data' 속성에 담긴 JSON 문자열을 파싱
                const parsedData = JSON.parse(dataString);
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + JSON.stringify(parsedData, null, 2);
            } catch (error) {
                // 'Data'가 JSON 형식이 아닌 경우
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + `Raw Data: ${dataString}`;
            }
        }
    };

    window.addEventListener('NativeEvent', handleNativeEvent);
  </script>
</body>
```

{% endtab %}
{% endtabs %}

* **(전면 비디오) 지원되는 이벤트**

{% tabs %}
{% tab title="Android" %}

| Event                                                                                 | 설명               |
| ------------------------------------------------------------------------------------- | ---------------- |
| <p><strong>OnInterstitialVideoAdLoaded</strong><br>(- placementId)</p>                | 전면 비디오 광고 로드 성공  |
| <p><strong>OnInterstitialVideoAdLoadFailed</strong><br>(- placementId, errorCode)</p> | 전면 비디오 광고 로드 실패  |
| <p><strong>OnInterstitialVideoAdOpened</strong><br>(- placementId)</p>                | 전면 비디오 광고 노출 성공  |
| <p><strong>OnInterstitialVideoAdOpenFailed</strong><br>(- placementId, errorCode)</p> | 전면 비디오  광고 노출 실패 |
| <p><strong>OnInterstitialVideoAdClosed</strong><br>(- placementId)</p>                | 전면 비디오 광고 닫기     |
| <p><strong>OnInterstitialVideoAdClicked</strong><br>(- placementId)</p>               | 전면 비디오 광고 클릭     |
| {% endtab %}                                                                          |                  |

{% tab title="iOS" %}

| Event                                                                             | 설명              |
| --------------------------------------------------------------------------------- | --------------- |
| <p><strong>OnInterstitialVideoLoadSuccess</strong><br>(- placementId)</p>         | 전면 비디오 광고 로드 성공 |
| <p><strong>OnInterstitialVideoLoadFail</strong><br>(- placementId, errorCode)</p> | 전면 비디오 광고 로드 실패 |
| <p><strong>OnInterstitialVideoShowSuccess</strong><br>(- placementId)</p>         | 전면 비디오 광고 노출 성공 |
| <p><strong>OnInterstitialVideoShowFail</strong><br>(- placementId)</p>            | 전면 비디오 광고 노출 실패 |
| <p><strong>OnInterstitialVideoClosed</strong><br>(- placementId)</p>              | 전면 비디오 광고 닫기    |
| {% endtab %}                                                                      |                 |
| {% endtabs %}                                                                     |                 |

### 4.4 리워드 비디오 광고

#### 4.4.1 리워드 비디오광고 요청

* 리워드 비디오광고 요청이 필요할 때 사용되는 API 입니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

<pre class="language-javascript"><code class="lang-javascript"><strong>if (Android) {
</strong>    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.loadRewardVideo(appKey, placementId);
} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    }
<strong>    window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
</strong>         action: "loadRewardVideo",
         appKey: appKey,
         placementID: placementID
    });
} 
</code></pre>

{% endtab %}
{% endtabs %}

* appKey : 앱키
* placementID: 지면키

#### 4.4.2 리워드 비디오광고 노출

* 리워드 비디오광고 노출 시 사용되는 API 입니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.showRewardVideo(appKey, placementID);
} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    }
    window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
        action: "showRewardVideo",
        appKey: appKey,
        placementID: placementID
    });
} 
```

{% endtab %}
{% endtabs %}

* appKey : 앱키
* placementID: 지면키

#### 4.4.3 리워드비디오 광고 이벤트

* 리워드비디오광고 관련 이벤트를 받고자 할 때 사용됩니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```html
<body>
  <!-- ... -->
  <script>
    const handleNativeEvent = (e: CustomEvent) => {
      const eventData = e.detail.data;
      console.log(e.detail.data);
      // {
      //   EventName: `OnInterstitialLoaded`,
      //   Data:`{"placementId":"5jb921hjk4j2k4b"}`,
      // }
      // Android
      if (eventData && typeof eventData === 'object' && eventData.EventName) {
            const eventName = eventData.EventName;
            const dataString = eventData.Data;

            try {
                // 'Data' 속성에 담긴 JSON 문자열을 파싱
                const parsedData = JSON.parse(dataString);
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + JSON.stringify(parsedData, null, 2);
            } catch (error) {
                // 'Data'가 JSON 형식이 아닌 경우
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + `Raw Data: ${dataString}`;
            }
        }
    };

    window.addEventListener('NativeEvent', handleNativeEvent);
  </script>
</body>
```

{% endtab %}
{% endtabs %}

* **(리워드 비디오) 지원되는 이벤트**

{% tabs %}
{% tab title="Android" %}

| Event                                                                           | 설명                |
| ------------------------------------------------------------------------------- | ----------------- |
| <p><strong>OnRewardVideoAdLoaded</strong><br>(- placementId)</p>                | 리워드 비디오 광고 로드 성공  |
| <p><strong>OnRewardVideoAdLoadFailed</strong><br>(- placementId, errorCode)</p> | 리워드 비디오 광고 로드 실패  |
| <p><strong>OnRewardVideoAdOpened</strong><br>(- placementId)</p>                | 리워드 비디오 광고 노출 성공  |
| <p><strong>OnRewardVideoAdOpenFailed</strong><br>(- placementId, errorCode)</p> | 리워드 비디오  광고 노출 실패 |
| <p><strong>OnRewardVideoPlayCompleted</strong><br>(- placementId)</p>           | 리워드 비디오 광고 재생 완료  |
| <p><strong>OnRewardVideoAdClosed</strong><br>(- placementId)</p>                | 리워드 비디오 광고 닫기     |
| <p><strong>OnRewardVideoAdClicked</strong><br>(- placementId)</p>               | 리워드 비디오 광고 클릭     |
| {% endtab %}                                                                    |                   |

{% tab title="iOS" %}

| Event                                                                       | 설명               |
| --------------------------------------------------------------------------- | ---------------- |
| <p><strong>OnRewardVideoLoadSuccess</strong><br>(- placementId)</p>         | 리워드 비디오 광고 로드 성공 |
| <p><strong>OnRewardVideoLoadFail</strong><br>(- placementId, errorCode)</p> | 리워드 비디오 광고 로드 실패 |
| <p><strong>OnRewardVideoShowSuccess</strong><br>(- placementId)</p>         | 리워드 비디오 광고 노출 성공 |
| <p><strong>OnRewardVideoShowFail</strong><br>(- placementId)</p>            | 리워드 비디오 광고 노출 실패 |
| <p><strong>OnRewardVideoPlayCompleted</strong><br>(- placementId)</p>       | 리워드 비디오 광고 재생 완료 |
| <p><strong>OnRewardVideoClosed</strong><br>(- placementId)</p>              | 리워드비디오 광고 닫기     |
| {% endtab %}                                                                |                  |
| {% endtabs %}                                                               |                  |

### 4.5 비디오 믹스 광고

#### 4.5.1 비디오 믹스광고 요청

* 비디오 믹스 광고 요청이 필요할 때 사용되는 API 입니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.loadVideoMix(appKey, placementID);
} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    }
    window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
       action: "loadVideoMix",
       appKey: appKey,
       placementID: placementID
    });
} 
```

{% endtab %}
{% endtabs %}

* appKey : 앱키
* placementID: 지면키

#### 4.5.2 비디오 믹스광고 노출

* 비디오 믹스광고 노출 시 사용되는 API 입니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.showVideoMix(appKey, placementID);
} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    }
    window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
         action: "showVideoMix",
         appKey: appKey,
         placementID: placementID
    });
} 
```

{% endtab %}
{% endtabs %}

* appKey : 앱키
* placementID: 지면키

#### 4.5.3 비디오 믹스광고 이벤트

* 비디오 믹스광고 관련 이벤트를 받고자 할 때 사용됩니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```html
<body>
  <!-- ... -->
  <script>
    const handleNativeEvent = (e: CustomEvent) => {
      const eventData = e.detail.data;
      console.log(e.detail.data);
      // {
      //   EventName: `OnInterstitialLoaded`,
      //   Data:`{"placementId":"5jb921hjk4j2k4b"}`,
      // }
      // Android
      if (eventData && typeof eventData === 'object' && eventData.EventName) {
            const eventName = eventData.EventName;
            const dataString = eventData.Data;

            try {
                // 'Data' 속성에 담긴 JSON 문자열을 파싱
                const parsedData = JSON.parse(dataString);
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + JSON.stringify(parsedData, null, 2);
            } catch (error) {
                // 'Data'가 JSON 형식이 아닌 경우
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + `Raw Data: ${dataString}`;
            }
        }
    };
    window.addEventListener('NativeEvent', handleNativeEvent);
  </script>
</body>
```

{% endtab %}
{% endtabs %}

* **(비디오 믹스) 지원되는 이벤트**

{% tabs %}
{% tab title="Android" %}

| Event                                                                                                           | 설명               |
| --------------------------------------------------------------------------------------------------------------- | ---------------- |
| <p><strong>OnVideoMixAdLoaded</strong><br>(- placementId)</p>                                                   | 비디오 믹스 광고 로드 성공  |
| <p><strong>OnVideoMixAdLoadFailed</strong><br>(- placementId, errorCode)</p>                                    | 비디오 믹스 광고 로드 실패  |
| <p><strong>OnVideoMixAdOpened</strong><br>(- placementId)</p>                                                   | 비디오 믹스 광고 노출 성공  |
| <p><strong>OnVideoMixAdOpenFailed</strong><br>(- placementId, errorCode)</p>                                    | 비디오 믹스  광고 노출 실패 |
| <p><strong>OnVideoMixPlayCompleted</strong><br>(- placementId)</p>                                              | 비디오 믹스 광고 재생 완료  |
| <p><strong>OnVideoMixAdClosed</strong><br>(- placementId<br> - campaignType : 2(전면), 4(리워드 비디오), 6(전면 비디오))</p> | 비디오 믹스 광고 닫기     |
| <p><strong>OnVideoMixAdClicked</strong><br>(- placementId)</p>                                                  | 비디오 믹스 광고 클릭     |
| {% endtab %}                                                                                                    |                  |

{% tab title="iOS" %}

| Event                                                                                                                | 설명              |
| -------------------------------------------------------------------------------------------------------------------- | --------------- |
| <p><strong>OnVideoMixLoadSuccess</strong><br>(- placementId)</p>                                                     | 비디오 믹스 광고 로드 성공 |
| <p><strong>OnVideoMixLoadFail</strong><br>(- placementId, errorCode)</p>                                             | 비디오 믹스 광고 로드 실패 |
| <p><strong>OnVideoMixShowSuccess</strong><br>(- placementId)</p>                                                     | 비디오 믹스 광고 노출 성공 |
| <p><strong>OnVideoMixShowFail</strong><br>(- placementId)</p>                                                        | 비디오 믹스 광고 노출 실패 |
| <p><strong>OnVideoMixPlayCompleted</strong><br>(- placementId<br> - campaignType : 2(전면), 4(리워드 비디오), 6(전면 비디오))</p> | 비디오 믹스 광고 재생 완료 |
| <p><strong>OnVideoMixClosed</strong><br>(- placementId)</p>                                                          | 비디오 믹스 광고 닫기    |
| {% endtab %}                                                                                                         |                 |
| {% endtabs %}                                                                                                        |                 |

### 4.6 보상형 광고 플러스 연동

#### 4.6.1 유저 식별값(USN) 입력

보상형 광고 플러스 기능을 활성화 및 활용하기 위해서는 반드시 유저의 식별값이 지정되어야 합니다.

유저 식별값은 리워드 지급 조건이 달성 되었을 때 완료 유저를 식별하기 위해 사용되는 값입니다.

{% hint style="danger" %}
**주의 사항**

1. 1명의 유저는 1개의 고유한 유저 식별값을 가져야 하며, 가변적인 값을 사용해서는 안됩니다.
2. 개인정보(이메일, 이름, 전화번호, 식별 가능한 유저 아이디 등)이 포함되어서는 안됩니다.
3. 한글, 특수 문자, 공백 등이 포함된 경우에는 반드시 URL 인코딩 처리를 하여 사용하여야 합니다.
4. 유저가 비디오 광고 로딩 전에 설정되어야 합니다.
5. <mark style="color:red;">**개발 서버에서 사용한 특정 USN값을 라이브 서버에서 유저가 할당받지 않도록 예외 처리를 하거나, 개발 서버에서 사용하는 USN은 별도 구분값을 설정해주셔야 합니다.**</mark>
   {% endhint %}

위 주의 사항에 유의하여 유저 식별값을 입력합니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.setUserId(userId);    
} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    }
    window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
        action: "userId",
        userId: userId
    });
}
```

{% endtab %}
{% endtabs %}

* userId : 유저 식별값

#### 4.6.2 보상형 광고 플러스 세팅 페이지 연동

유저의 CS 문의 및 현재 리워드 참여 횟수 확인을 위해서는 세팅 페이지 연동이 반드시 필요합니다.

버튼 등의 진입 경로를 만든 후, 아래 API를 연동하여 유저가 세팅 페이지 진입이 가능하도록 합니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.openRewardAdPlusPage('');    
} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    }
    window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
        action: 'openRewardAdPlusPage',
        appKey: appKey
    });
}
```

{% endtab %}
{% endtabs %}

#### 4.6.3 보상형 광고 플러스 이벤트 처리

보상형 광고 플러스에서 발생되는 이벤트를 전달 받고자 할 경우 아래 api를 호출합니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.setRewardAdPlusEventListener();    
} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    }
    window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
        action: 'setRewardAdPlusEventListener'
    });
}
```

{% endtab %}
{% endtabs %}

api 를 호출 한 뒤에는 아래의 이벤트를 전달 받을 수 있습니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
// 위 api 호출에 대한 이벤트 콜백 처리
<body>
  <!-- ... -->
  <script>
    const handleNativeEvent = (e: CustomEvent) => {
      const eventData = e.detail.data;
      console.log(e.detail.data);
      // {
      //   EventName: `OnClosedRewardAdPlusPage`,
      //   Data:`{}`
      // }
      
      // {
      //   EventName: `OnRewardAdPlusEventResult`,
      //   Data:`{"resultCode":1,"resultMessage":"포인트박스 적립에 성공하였습니다."}`
      // }
      
      // Android
      if (eventData && typeof eventData === 'object' && eventData.EventName) {
            const eventName = eventData.EventName;
            const dataString = eventData.Data;

            try {
                // 'Data' 속성에 담긴 JSON 문자열을 파싱
                const parsedData = JSON.parse(dataString);
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + JSON.stringify(parsedData, null, 2);
            } catch (error) {
                // 'Data'가 JSON 형식이 아닌 경우
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + `Raw Data: ${dataString}`;
            }
        }
    };
    window.addEventListener('NativeEvent', handleNativeEvent);
  </script>
</body>
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="Android" %}

| Event                         | 설명                         |
| ----------------------------- | -------------------------- |
| **OnClosedRewardAdPlusPage**  | 보상형 광고 플러스 세팅 페이지 닫기 이벤트   |
| **OnRewardAdPlusEventResult** | 보상형 광고 플러스 1차 광고 리워드 처리 결과 |
| {% endtab %}                  |                            |

{% tab title="iOS" %}

| Event                                                                    | 설명              |
| ------------------------------------------------------------------------ | --------------- |
| <p><strong>OnVideoMixLoadSuccess</strong><br>(- placementId)</p>         | 비디오 믹스 광고 로드 성공 |
| <p><strong>OnVideoMixLoadFail</strong><br>(- placementId, errorCode)</p> | 비디오 믹스 광고 로드 실패 |
| {% endtab %}                                                             |                 |
| {% endtabs %}                                                            |                 |

#### 4.6.4 보상형 광고 플러스 사용자 정보 조회

현재 유저의 사용 정보를 직접 조회하고자 할 땐, 아래의 API를 활용 가능합니다.

* 현재사용자의 전체 사용 정보 조회

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.getRewardAdPlusUserMediaStatus();    
} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    }
    window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
        action: "getRewardAdPlusUserMediaStatus",
        appKey: appKey
    });
}
```

{% endtab %}
{% endtabs %}

api 를 호출 한 뒤에는 아래의 이벤트를 전달 받을 수 있습니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

<pre class="language-javascript"><code class="lang-javascript"><strong>&#x3C;body>
</strong>  &#x3C;!-- ... -->
  &#x3C;script>
    const handleNativeEvent = (e: CustomEvent) => {
      const eventData = e.detail.data;
      console.log(e.detail.data);
      // {
      //   EventName: `OnRewardAdPlusUserMediaStatus`,
      //   Data:`{"result":true,"totalBoxCount":2,"placementStatusList":[{"placementId":"SfVrRgdywQkNYPf","dailyUserLimit":0,"dailyUserCount":10},{"placementId":"WMu8uCGvGKbhsZ3","dailyUserLimit":2,"dailyUserCount":2},{"placementId":"s3tGYNjFCxn6cu1","dailyUserLimit":0,"dailyUserCount":5},{"placementId":"vmaaDcGfWhmtbzDf","dailyUserLimit":0,"dailyUserCount":5}]}`
      // }
      // Android
      if (eventData &#x26;&#x26; typeof eventData === 'object' &#x26;&#x26; eventData.EventName) {
            const eventName = eventData.EventName;
            const dataString = eventData.Data;

            try {
                // 'Data' 속성에 담긴 JSON 문자열을 파싱
                const parsedData = JSON.parse(dataString);
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + JSON.stringify(parsedData, null, 2);
            } catch (error) {
                // 'Data'가 JSON 형식이 아닌 경우
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + `Raw Data: ${dataString}`;
            }
        }
    };
    window.addEventListener('NativeEvent', handleNativeEvent);
  &#x3C;/script>
&#x3C;/body>
</code></pre>

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="Android" %}

| Event                               | 설명                 |
| ----------------------------------- | ------------------ |
| **OnRewardAdPlusUserMediaStatus**   | 현재사용자의 전체 사용 정보 조회 |
| ㄴtotalBoxCount                      | 전체 받은 포인트 박스       |
| ㄴplacementStatusList                |                    |
| ㄴplacementStatusList.placementId    | 지면키                |
| ㄴplacementStatusList.dailyUserLimit | 일별 유저 한도           |
| ㄴplacementStatusList.dailyUserCount | 일별 유저 참여 횟수        |
| {% endtab %}                        |                    |

{% tab title="iOS" %}

| Event                             | 설명                 |
| --------------------------------- | ------------------ |
| **OnRewardAdPlusUserMediaStatus** | 현재사용자의 전체 사용 정보 조회 |
| ㄴtotalBoxCount                    | 전체 받은 포인트 박스       |
| ㄴplacementStatusList              |                    |
| ㄴplacementStatusList.placementID  | 지면키                |
| ㄴplacementStatusList.limit        | 일별 유저 한도           |
| ㄴplacementStatusList.current      | 일별 유저 참여 횟수        |
| {% endtab %}                      |                    |
| {% endtabs %}                     |                    |

* 사용자의 특정 지면에 대한 사용 정보 조회

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
if (Android) {
    if (typeof window.AdPopcornSSPJsBridge === 'undefined') {
        return;
    }
    window.AdPopcornSSPJsBridge.getRewardAdPlusUserPlacementStatus('placementId');    
} 
else if (ios) {
    if (!window.webkit?.messageHandlers?.AdPopcornSSPJsBridge) {
        return;
    }
    window.webkit.messageHandlers.AdPopcornSSPJsBridge.postMessage({
        action: "getRewardAdPlusUserPlacementStatus",
        appKey: appKey,
        placementID: placementID           
        });
}
```

{% endtab %}
{% endtabs %}

api 를 호출 한 뒤에는 아래의 이벤트를 전달 받을 수 있습니다.

{% tabs %}
{% tab title="Android & iOS WebView" %}

```javascript
<body>
  <!-- ... -->
  <script>
    const handleNativeEvent = (e: CustomEvent) => {
      const eventData = e.detail.data;
      console.log(e.detail.data);
      // {
      //   EventName: `OnRewardAdPlusUserPlacementStatus`,
      //   Data:`{"result":true,"placementId":"WMu8uCGvGKbhsZ3","dailyUserLimit":2,"dailyUserCount":2}`
      // }
      // Android
      if (eventData && typeof eventData === 'object' && eventData.EventName) {
            const eventName = eventData.EventName;
            const dataString = eventData.Data;

            try {
                // 'Data' 속성에 담긴 JSON 문자열을 파싱
                const parsedData = JSON.parse(dataString);
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + JSON.stringify(parsedData, null, 2);
            } catch (error) {
                // 'Data'가 JSON 형식이 아닌 경우
                logMessage = `📢 ${eventName} 이벤트 발생: \n` + `Raw Data: ${dataString}`;
            }
        }
    };
    window.addEventListener('NativeEvent', handleNativeEvent);
  </script>
</body>
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="Android" %}

| Event                                 | 설명                      |
| ------------------------------------- | ----------------------- |
| **OnRewardAdPlusUserPlacementStatus** | 현재 사용자의 특정  지면 사용 정보 조회 |
| ㄴplacementId                          | 지면키                     |
| ㄴdailyUserLimit                       | 일별 유저 한도                |
| ㄴdailyUserCount                       | 일별 유저 참여 횟수             |
| {% endtab %}                          |                         |

{% tab title="iOS" %}

| Event                                 | 설명                      |
| ------------------------------------- | ----------------------- |
| **OnRewardAdPlusUserPlacementStatus** | 현재 사용자의 특정  지면 사용 정보 조회 |
| ㄴplacementId                          | 지면키                     |
| ㄴdailyUserLimit                       | 일별 유저 한도                |
| ㄴdailyUserCount                       | 일별 유저 참여 횟수             |
| {% endtab %}                          |                         |
| {% endtabs %}                         |                         |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://adpopcornssp.gitbook.io/ssp-sdk/sdk/hybrid-app.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
