npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@motiv-i/react-native-exelbid

v0.2.0

Published

React Native package wrapping the ExelBid iOS/Android SDKs (New Architecture / Fabric).

Downloads

282

Readme

@motiv-i/react-native-exelbid

ExelBid 광고 SDK(iOS/Android)를 React Native에서 사용하기 위한 패키지입니다. 배너 · 네이티브 · 전면(Interstitial) · 비디오 광고와 미디에이션(워터폴) 을 지원합니다.

플랫폼 지원

| 광고 유형 | iOS | Android | |---|:---:|:---:| | Banner / Native / Interstitial / Video | ✅ | ✅ | | Mediation (Banner / Native / Interstitial / Video) | ✅ | ✅ |

같은 JS/TS 코드로 두 플랫폼이 동작하며, 광고 단위 ID만 플랫폼별로 다르게 관리하면 됩니다. 최소 버전은 iOS 13.0+ / Android API 24+ 이고 New Architecture (Fabric / TurboModule) 전용입니다.

ExelBid 광고는 추가 설정 없이 바로 사용할 수 있습니다. 미디에이션으로 AdMob · FAN · AdFit 등 외부 네트워크를 함께 운영할 수 있습니다.

앱 호스트는 React만 작성합니다. Android/iOS 광고 로직은 이 패키지가 감싸는 컴파일된 네이티브 SDK 안에 있고, 이 패키지는 그 위의 얇은 브리지입니다.

@motiv-i/react-native-exelbid  (JS/TS — 앱이 보는 유일한 부분)
   ├─ Android → com.onnuridmc.exelbid:exelbid   (AAR, 컴파일됨)    ★ 광고 로직
   └─ iOS     → ExelBid_iOS_Swift (XCFramework, 컴파일됨)          ★ 광고 로직

목차

  1. 요구 사항
  2. 설치
  3. iOS 프로젝트 설정
  4. Android 프로젝트 설정
  5. SDK 초기화 · ATT
  6. 배너 광고
  7. 네이티브 광고
  8. 전면 광고 (Interstitial)
  9. 비디오 광고
  10. 광고 옵션 (AdOptions)
  11. 미디에이션
  12. 에러 처리 (AdError)
  13. 공통 API 요약
  14. 샘플 앱
  15. 트러블슈팅
  16. 부록: AdMob 옵션 (선택)

요구 사항

| 항목 | 버전 | |---|---| | React Native | 0.76+ (New Architecture 활성화) | | iOS | 13.0+ · ExelBid_iOS_Swift >= 3.0.8 | | Android | API 24+ (RN 0.76 기준) · com.onnuridmc.exelbid:exelbid 2.0.2 · Java 17 |

ExelBid 광고 표시용 Activity와 ACCESS_NETWORK_STATE 권한은 이 패키지의 AndroidManifest.xml호스트 앱에 자동 머지하므로 별도 선언이 필요 없습니다.


설치

yarn add @motiv-i/react-native-exelbid
# 또는
npm install @motiv-i/react-native-exelbid

이 패키지는 New Architecture 전용입니다. 아래처럼 New Arch를 켠 뒤 빌드하세요.

  • Androidandroid/gradle.propertiesnewArchEnabled=true

  • iOSpod install 시 New Arch 켜기:

    cd ios && RCT_NEW_ARCH_ENABLED=1 pod install

네이티브 SDK(Android AAR / iOS XCFramework)는 이 패키지가 오토링크로 함께 가져오므로 호스트 앱이 SDK 의존성을 직접 추가할 필요는 없습니다(미디에이션 3rd-party 네트워크만 예외 — 미디에이션 참고).


iOS 프로젝트 설정

네이티브 SDK는 CocoaPods 오토링크로 통합됩니다. pod install만 하면 되고 호스트 Podfilepod 'ExelBid_iOS_Swift'를 직접 추가할 필요는 없습니다.

cd ios && RCT_NEW_ARCH_ENABLED=1 pod install

App Tracking Transparency (필수)

iOS에서 광고 식별자(IDFA)를 사용하려면 ATT 권한 요청이 필요합니다. 두 가지를 호스트 앱이 직접 처리해야 합니다.

1. Info.plist에 권한 안내 문구 추가 (필수)

<key>NSUserTrackingUsageDescription</key>
<string>맞춤형 광고를 제공하기 위해 사용자 활동을 추적합니다.</string>

⚠️ 이 키가 없으면 requestTrackingAuthorization() 호출 순간 iOS가 앱을 **강제 종료(SIGKILL, TCC privacy violation)**합니다 — 앱이 켜자마자 죽은 것처럼 보입니다.

2. 적절한 시점(앱 활성화 직후 등)에 ATT 요청 (필수)

이 패키지는 ATT를 자동으로 띄우지 않습니다. 호출 시점은 앱이 결정합니다 (SDK 초기화 · ATT 참고).

SKAdNetwork (권장)

광고 성과 측정(어트리뷰션)을 위해 매체사에서 제공받은 SKAdNetwork ID 목록을 Info.plist에 등록하세요(선택, 정확한 성과 측정에 권장).

<key>SKAdNetworkItems</key>
<array>
  <dict>
    <key>SKAdNetworkIdentifier</key>
    <string>xxxxxxxxxx.skadnetwork</string>
  </dict>
</array>

AdMob 등 외부 네트워크를 미디에이션으로 함께 쓸 때는 각 네트워크의 GADApplicationIdentifier 등 추가 설정이 필요합니다 — 미디에이션 참고.


Android 프로젝트 설정

ExelBid Android SDK와 필수 라이브러리·권한·광고 표시용 액티비티를 이 패키지가 모두 번들·머지하므로, 호스트 앱에서 별도 의존성/권한 추가는 필요 없습니다.

최소 SDK

minSdkVersion24 이상이어야 합니다(RN 0.76 기준). AndroidX가 활성화되어 있어야 합니다(최신 RN 프로젝트는 기본 활성화).

New Architecture

android/gradle.propertiesnewArchEnabled=true가 있어야 합니다.

AdMob · FAN · AdFit 등 외부 네트워크를 함께 쓰려면 미디에이션 섹션의 설정을 따르세요. AdMob을 사용할 때만 AdMob 앱 ID 설정이 필요합니다.


SDK 초기화 · ATT

import { Exelbid } from '@motiv-i/react-native-exelbid';

Exelbid.setLogLevel('debug'); // 'verbose' | 'debug' | 'info' | 'warning' | 'error'
const version = await Exelbid.getSdkVersion();
await Exelbid.requestTrackingAuthorization(); // iOS ATT (Android은 항상 authorized)

requestTrackingAuthorization()TrackingAuthorizationStatus (notDetermined · restricted · denied · authorized)를 반환하며, getTrackingAuthorizationStatus()로 프롬프트 없이 현재 상태만 조회할 수 있습니다.

const status = await Exelbid.requestTrackingAuthorization();
if (status === 'authorized') {
  // IDFA 사용 가능
}

iOS 14 미만에는 ATT 개념이 없어 항상 authorized를 반환합니다. Android에서도 항상 authorized를 반환하므로, 위 코드를 두 플랫폼 공통으로 호출해도 안전합니다.

iOS는 Info.plistNSUserTrackingUsageDescription이 반드시 있어야 합니다 (없으면 요청 순간 앱이 SIGKILL로 종료). iOS 프로젝트 설정 참고.


배너 광고

배너는 진짜 네이티브 뷰입니다 — <View>처럼 아무 레이아웃에나 넣으면 됩니다. size로 지정한 영역만큼 차지합니다(기본 320×50).

import { ExelbidBanner } from '@motiv-i/react-native-exelbid';

<ExelbidBanner
  adUnitId="YOUR_BANNER_UNIT"
  size={{ width: 320, height: 50 }}
  options={{ testing: true }}
  onLoad={() => {}}
  onFail={(e) => console.warn(e.code, e.message)}
  onClick={() => {}}
  onLeaveApp={() => {}}
  onClickFinish={() => {}}
/>

동작 파라미터

| prop | 기본 | 설명 | |---|:---:|---| | autoLoad | true | 마운트 즉시 첫 광고를 요청. false면 ref의 load()로 직접 트리거(예: ATT 동의 이후로 미루기). | | fullWebView | false | true면 크리에이티브가 배너 영역을 꽉 채움. | | options | — | 타깃팅/테스트 옵션(AdOptions). |

ref로 수동 제어:

const ref = useRef(null);
<ExelbidBanner ref={ref} adUnitId="…" autoLoad={false} />;
// ref.current.load();  // 요청(또는 강제 재요청)
// ref.current.stop();  // 진행 중 요청 취소 + 자동 갱신 중지

이벤트: onLoad · onFail · onClick · onLeaveApp · onClickFinish.


네이티브 광고

광고 레이아웃을 React로 자유롭게 구성합니다. 각 <ExelbidNativeAd.*> 슬롯은 역할 태그가 달린 실제 네이티브 뷰가 되고, 컨테이너가 그 뷰들을 SDK에 넘기면 SDK가 크리에이티브를 그려 넣고 임프레션/클릭 추적을 겁니다. 앱에는 네이티브 코드가 전혀 없습니다.

각 슬롯은 실제 네이티브 뷰라서 스크롤·레이아웃 변경·리마운트에도 광고가 어긋나지 않습니다. 좌표를 계산해 맞춰 줄 필요가 없습니다.

import { ExelbidNativeAd } from '@motiv-i/react-native-exelbid';
import { View } from 'react-native';

<ExelbidNativeAd
  adUnitId="YOUR_NATIVE_UNIT"
  options={{ testing: true }}
  desiredAssets={['title', 'main', 'icon', 'ctatext']}
  onLoad={(data) => console.log(data.title, data.rating)}
  onFail={(e) => console.warn(e.message)}
  onImpression={() => {}}
  onClick={() => {}}
>
  <View style={{ flexDirection: 'row' }}>
    <ExelbidNativeAd.Icon style={{ width: 44, height: 44 }} />
    <ExelbidNativeAd.Title style={{ flex: 1, height: 20 }} />
  </View>
  <ExelbidNativeAd.Media style={{ height: 160 }} />
  <ExelbidNativeAd.CallToAction style={{ height: 44 }} />
</ExelbidNativeAd>;

슬롯 컴포넌트

| 컴포넌트 | 자산 | 종류 | |---|---|---| | ExelbidNativeAd.Title | 제목 | 텍스트 | | ExelbidNativeAd.Body | 본문 | 텍스트 | | ExelbidNativeAd.CallToAction | CTA 버튼 텍스트 | 텍스트 | | ExelbidNativeAd.Sponsored | 광고주 표기 (Sponsored) | 텍스트 | | ExelbidNativeAd.DisplayUrl | 표시 URL | 텍스트 | | ExelbidNativeAd.Media | 메인 크리에이티브 (이미지 또는 동영상) | 미디어 | | ExelbidNativeAd.Icon | 아이콘 | 이미지 | | ExelbidNativeAd.Logo | 로고 이미지 | 이미지 | | ExelbidNativeAd.PrivacyIcon | 프라이버시 아이콘 | 이미지 |

  • 범용 형태 <ExelbidNativeAsset type="title" />로도 쓸 수 있습니다.
  • 메인 크리에이티브는 Media 하나로 통합되어 있습니다. 정적 이미지든 동영상이든 이 슬롯에 채워집니다(별도 메인 이미지 슬롯은 없습니다).

이벤트: onLoad(data) · onFail · onClick · onImpression · onImpression50 · onImpression100 · onLeaveApp · onClickFinish.

슬롯 스타일 — 하나의 style

슬롯 텍스트/이미지는 SDK가 그리는 네이티브 뷰라서 RN style이 글자색·폰트까지 직접 닿지 못합니다. 그래도 호스트는 style 하나에 전부 적으면 됩니다 — 래퍼가 RN이 적용할 박스/레이아웃과 OS-native가 적용할 콘텐츠로 자동 분리합니다.

<ExelbidNativeAd.Title
  style={{
    // 레이아웃/박스 → RN이 슬롯 바깥 뷰에 적용 (iOS·Android 모두)
    backgroundColor: '#F4F4F5',
    borderRadius: 8,
    borderWidth: 1,
    borderColor: '#E4E4E7',
    padding: 8,
    // 콘텐츠 → SDK가 그리는 안쪽 뷰에 전달
    color: '#111',
    fontSize: 16,
    fontWeight: '700',
    numberOfLines: 1,
    ellipsizeMode: 'tail',
  }}
/>

<ExelbidNativeAd.Media style={{ height: 160, resizeMode: 'cover' }} />

네이티브 inner view로 전달되는 콘텐츠 prop: color, fontFamily, fontSize, fontWeight, textAlign, numberOfLines, ellipsizeMode, resizeMode(이미지). 나머지(size/margin/padding/backgroundColor/border*/borderRadius …)는 표준 RN ViewStyle로 슬롯 바깥 뷰에 적용됩니다. 즉 테두리·둥근 모서리·배경은 iOS·Android 모두 동작합니다. 단독 네이티브와 미디에이션 네이티브 슬롯 모두 동일합니다.

resizeMode는 미디어의 채움 방식입니다. cover는 종횡비 유지 + 슬롯을 꽉 채우고 넘치는 가장자리를 자르며, contain은 종횡비 유지 + 전체를 슬롯 안에 맞춥니다(여백 가능). 텍스트가 아니라 이미지/미디어 슬롯에만 의미가 있습니다.

커스텀 폰트는 iOS·Android 앱에도 등록해야 적용됩니다

광고의 글자는 RN이 아니라 휴대폰 운영체제(iOS·Android)가 직접 그립니다. 그래서 fontFamily로 폰트를 지정하려면, 그 폰트 파일이 iOS 앱과 Android 앱 양쪽에 등록 되어 있어야 합니다. RN(JS)에서 폰트 이름만 넘기면 광고 글자에는 반영되지 않고, 등록되지 않은 폰트는 휴대폰 기본 폰트로 대체됩니다.

| 폰트 종류 | 동작 | |---|---| | 휴대폰에 기본 내장된 폰트 ('Georgia' 등) | 추가 등록 없이 바로 적용 ✅ | | 직접 추가한 커스텀 폰트 | 아래처럼 iOS·Android 앱에 각각 등록해야 적용 |

  • iOS — 폰트 파일을 앱(Runner) 타깃에 넣고 Info.plistUIAppFonts에 등록. fontFamily에는 PostScript 이름을 씁니다(UIFont(name:) 기준).
  • Android — 폰트 파일을 앱의 res/font/<name>.ttf(또는 .otf)에 넣습니다. fontFamily에는 확장자를 뺀 **리소스 이름(<name>)**을 씁니다 (resources.getIdentifier(name, "font", …) 기준).

요청할 자산 지정 (desiredAssets)

desiredAssets로 SDK가 채울 자산을 좁힐 수 있습니다(iOS만 적용, Android는 SDK 한계로 무시됨). 값은 SDK 자산명이라 슬롯 컴포넌트명과 다릅니다: title · desc · desc2 · ctatext · sponsored · displayUrl · phone · address · icon · main · logo · rating · likes · downloads · price · salePrice · video.

<ExelbidNativeAd adUnitId="…" desiredAssets={['title', 'main', 'icon', 'ctatext']}>

데이터 전용 자산 (onLoad payload)

렌더 슬롯이 없는 값(별점·가격·다운로드 수 등)은 onLoaddata로 받아 평범한 RN 컴포넌트로 직접 그립니다. dataNativeAdData입니다.

| 필드 | 필드 | 필드 | |---|---|---| | title | body | secondaryBody | | callToAction | sponsored | displayUrl | | phone | address | iconImageUrl | | mainImageUrl | logoImageUrl | rating | | likes | downloads | price | | salePrice | hasVideo | |

iOS는 17필드 전부를 채웁니다. Android는 SDK 한계로 7필드 (secondaryBody/phone/address/likes/downloads/price/salePrice)만 채웁니다.


전면 광고 (Interstitial)

뷰가 아니라 명령형 핸들입니다. create()load() → (onLoad) → present() 순서로 동작합니다.

import { ExelbidInterstitialAd } from '@motiv-i/react-native-exelbid';

const ad = ExelbidInterstitialAd.create({
  adUnitId: 'YOUR_UNIT',
  options: { testing: true },
});

const off = ad.addListener((e) => {
  if (e.event === 'onLoad') ad.present();
  if (e.event === 'onFail') console.warn(e.error?.code, e.error?.message);
});

ad.load();

// 정리
// off();
// ad.destroy();          // id/리스너까지 완전 해제
// ad.stop();             // 광고 객체만 해제(같은 인스턴스로 재로드 가능)

이벤트: onLoad · onFail · onWillAppear · onDidAppear · onWillDisappear · onDidDisappear · onClick · onLeaveApp · onClickFinish (Android은 SDK 한계로 일부만 발사).

createfullWebView: true를 주면 크리에이티브가 화면을 꽉 채웁니다. isReady()로 표시 가능 여부를 확인할 수 있습니다.


비디오 광고

전면과 동일한 명령형 핸들입니다. iOS는 재생 진행률(onProgress)도 전달합니다.

import { ExelbidVideoAd } from '@motiv-i/react-native-exelbid';

const ad = ExelbidVideoAd.create({
  adUnitId: 'YOUR_UNIT',
  options: { testing: true, videoSkipMin: 5 },
});

const off = ad.addListener((e) => {
  if (e.event === 'onLoad') ad.present();
  if (e.event === 'onProgress') console.log(`${e.percent}%`); // iOS 전용
  if (e.event === 'onFail') console.warn(e.error?.message);
});

ad.load();

// off(); ad.destroy();

이벤트: onLoad · onFail · onProgress(iOS) · onWillAppear · onDidAppear · onWillDisappear · onDidDisappear · onClick · onLeaveApp.

onProgress(재생 진행률)는 iOS에서만 발생합니다. Android는 진행률 콜백을 지원하지 않습니다.

Android 비디오 재생에 필요한 의존성은 이 패키지가 번들합니다 — 호스트 앱에서 별도로 추가할 필요가 없습니다.


광고 옵션 (AdOptions)

모든 광고의 options(전면/비디오는 createoptions)로 타깃팅/테스트 설정을 전달합니다. 모두 선택이며, 설정한 항목만 적용됩니다.

const options = {
  keywords: { category: 'sports' },
  yearOfBirth: 1990,
  gender: 'male', // 'unspecified' | 'male' | 'female'
  location: { latitude: 37.5, longitude: 127.0 },
  coppa: false, // 아동 대상 여부 (COPPA)
  testing: true, // 테스트 광고 (개발 중 권장)
  videoSkipMin: 15, // (비디오) 스킵 가능 최소 재생 시간(초)
  videoSkipAfter: 10, // (비디오) 자동 스킵 시점(초) — iOS 전용
};

미디에이션

미디에이션이란?

하나의 광고 자리에 여러 광고 네트워크를 연결해 두고, 순서대로 시도해 가장 먼저 광고를 채우는 네트워크를 노출하는 방식입니다(워터폴). 한 네트워크가 실패하면 다음 네트워크로 자동 폴백하므로, 단일 네트워크보다 노출률(필레이트)이 올라갑니다.

  • 시도 순서(우선순위)는 ExelBid 콘솔(서버)에서 설정합니다. 앱 코드로 순서를 정하지 않습니다.
  • ExelBid 네트워크는 기본 포함(추가 설정 불필요)입니다.
  • AdMob · FAN · AdFit은 사용하려면 호스트 앱이 직접 연결해야 합니다. 연결(등록)하지 않은 네트워크는 워터폴에서 자동으로 건너뜁니다(adapterNotRegistered).

왜 직접 연결해야 하나요? 이 패키지는 각 네트워크의 어댑터 코드만 제공하고, 실제 네트워크 SDK는 포함하지 않습니다. 그래서 쓰지 않는 네트워크의 SDK가 앱에 들어가지 않습니다(앱 용량·정책 부담 최소화). 스코프: AdMob(전 포맷) · FAN(전 포맷) · AdFit(배너+네이티브 — SDK가 전면/비디오 미지원).

설정 순서

사용할 외부 네트워크(AdMob/FAN/AdFit)마다 아래를 진행합니다. ExelBid만 쓸 경우 1·5만 하면 됩니다(2~4 불필요).

  1. ExelBid 콘솔에서 해당 광고 단위의 워터폴(네트워크·순서)을 구성
  2. 네트워크 SDK 추가 — 앱에 해당 네트워크 SDK 의존성 추가 (Android / iOS)
  3. 네트워크별 필수 설정 — 앱 ID 등 (네트워크별 필수 설정)
  4. 어댑터 등록 — 이 패키지가 제공하는 모듈을 등록
  5. 코드 작성ExelbidMediated* 컴포넌트/클래스로 광고 요청 (코드에서 사용)

Android — 3rd-party 연결

(1) android/app/build.gradle에 사용할 네트워크 SDK만 추가(어댑터는 이 패키지에 포함돼 있고, 네트워크 SDK만 호스트가 추가):

dependencies {
  implementation "com.google.android.gms:play-services-ads:23.4.0"   // AdMob
  implementation "com.facebook.android:audience-network-sdk:6.20.0"  // FAN
  implementation "com.kakao.adfit:ads-base:3.12.9"                   // AdFit
}

AdFit을 쓰면 Kakao maven 저장소가 필요합니다 — 루트 android/build.gradleallprojects.repositories(또는 settings.gradle)에 추가:

maven { url "https://devrepo.kakao.com/nexus/content/groups/public/" }

(2) 앱 시작 시 사용할 네트워크만 등록(JS 한 줄):

import { Exelbid } from '@motiv-i/react-native-exelbid';

Exelbid.registerMediationNetworks(['admob', 'fan', 'adfit']);

⚠️ SDK를 추가하지 않고 등록만 하면, 그 네트워크 광고를 로드하는 시점에 NoClassDefFoundError가 발생합니다. (1)·(2)는 항상 같이 하세요.

iOS — 3rd-party 연결

iOS는 어댑터가 별도 패키지라 호스트 앱에 연결 + AppDelegate에서 등록합니다(JS registerMediationNetworks는 iOS에서 동작하지 않으니 아래처럼 Swift에서 등록하세요). CocoaPods 팟은 AdMob·FAN만 제공합니다(AdFit은 SwiftPM 전용).

(1) ios/Podfile의 앱 타깃에 추가 후 RCT_NEW_ARCH_ENABLED=1 pod install:

pod 'ExelBid_Mediation_Adapter/AdMob'
pod 'ExelBid_Mediation_Adapter/FAN'

(2) Swift 코드에서 등록(메타타입이라 Swift 필요 — AppDelegate를 Swift로 두거나 Swift 헬퍼를 추가):

import ExelBidSDK
import ExelBidMediationAdapter   // CocoaPods 단일 모듈로 AdMob·FAN 어댑터 노출
import GoogleMobileAds

// application(_:didFinishLaunchingWithOptions:) 안, 광고 요청 전에:
ExelBidMediationKit.shared.register(modules: [
  AdMobMediationModule.self,
  FANMediationModule.self,
])
MobileAds.shared.start(completionHandler: nil) // AdMob 초기화

ObjC++ AppDelegate라면 Swift 헬퍼(@objc class)에 위 등록을 넣고 #import "<앱>-Swift.h" 후 호출합니다. 첫 Swift 파일을 추가하면 Xcode가 -Swift.h를 자동 생성하므로 브리징 헤더는 불필요합니다. (example/iosMediationBootstrap.swift 참고.)

ExelBid 본가 네트워크는 이 패키지가 자동 등록하므로 호스트는 3rd-party만 등록하면 됩니다.

네트워크별 필수 설정

네트워크마다 앱에 추가로 넣어야 하는 식별자/설정이 다릅니다.

| 네트워크 | Android 필수 | iOS 필수 | 비고 | |---|---|---|---| | AdMob | AndroidManifest.xmlcom.google.android.gms.ads.APPLICATION_ID 메타데이터 | Info.plistGADApplicationIdentifier + MobileAds.shared.start(...) | 앱 ID는 AdMob 콘솔 발급(광고 단위 ID와 다름). 없으면 init 시 크래시 | | FAN (Meta) | 추가 식별자 없음 (AndroidX 필요) | 추가 식별자 없음 (SKAdNetwork·ATT 권장) | 앱 단위 ID 없이 placement ID로 동작. 실기기 테스트는 Meta 테스트 디바이스 등록 필요 | | AdFit (Kakao) | Kakao maven 저장소 | (CocoaPods 미지원 — SwiftPM 전용) | 앱 단위 ID 없이 client ID로 동작. 전면/비디오 미지원(배너·네이티브만) |

AdMob App ID 설정 예시

  • Android — AndroidManifest.xml<application> 안:

    <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID"
      android:value="ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY" />
  • iOS — Info.plist:

    <key>GADApplicationIdentifier</key>
    <string>ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY</string>

공통: 모든 네트워크의 실제 낙찰은 ExelBid 콘솔 워터폴에 해당 네트워크 라인아이템이 등록되어 있어야 발생합니다. 광고 단위 ID(placement/client ID 등)는 보통 콘솔 설정에 포함됩니다.

코드에서 사용

미디에이션 광고는 일반 광고와 동일한 컴포넌트/클래스에 Mediated가 붙은 버전을 씁니다: ExelbidMediatedBanner · ExelbidMediatedNativeAd · ExelbidMediatedInterstitialAd · ExelbidMediatedVideoAd. JS API는 iOS·Android 공통입니다.

공통 추가 옵션:

  • perNetworkTimeout — 네트워크당 타임아웃(초). 초과 시 다음 네트워크로 폴백.
  • onWinningNetwork / winningNetwork — 낙찰(노출 성공)된 네트워크 이름.
  • onWaterfall / WaterfallEvent — 워터폴 진행 추적(아래).

Mediated Banner / Native (Fabric 뷰)

import { ExelbidMediatedBanner } from '@motiv-i/react-native-exelbid';

<ExelbidMediatedBanner
  adUnitId="YOUR_UNIT"
  size={{ width: 320, height: 50 }}
  perNetworkTimeout={5}
  onWinningNetwork={(net) => console.log('낙찰:', net)}
  onWaterfall={(e) => console.log(e.type)} // fetching→trying→won/lost/noFill
  onLoad={() => {}}
  onFail={(e) => console.warn(e.message)}
/>;

ExelbidMediatedNativeAd는 일반 네이티브와 동일한 슬롯·스타일·onLoad(data) APIperNetworkTimeout·onWinningNetwork만 추가됩니다.

Mediated Interstitial / Video (명령형) — 일반 전면/비디오와 같은 create/load/present 패턴이며, 이벤트에 onWaterfall이 추가되고 onLoad 이벤트 데이터에 winningNetwork가 실립니다.

import { ExelbidMediatedVideoAd } from '@motiv-i/react-native-exelbid';

const ad = ExelbidMediatedVideoAd.create({
  adUnitId: 'YOUR_UNIT',
  perNetworkTimeout: 5,
});

const off = ad.addListener((e) => {
  if (e.event === 'onWaterfall') console.log(e.waterfall?.type);
  if (e.event === 'onLoad') {
    console.log('낙찰:', e.winningNetwork);
    ad.present();
  }
});

ad.load();
// off(); ad.destroy();  // (전면/비디오 미디에이션은 stop()도 지원)

WaterfallEvent

onWaterfall이 전달하는 이벤트(type으로 분기). 워터폴 디버깅에 유용하며, decodeWaterfallEvent/formatWaterfallEvent 헬퍼도 export됩니다.

| type | 의미 | 추가 필드 | |---|---|---| | fetching | 워터폴 조회 시작 | — | | fetched | 네트워크 목록 수신 | networks: string[] | | trying | 특정 네트워크 시도 | network, position, total, unitId | | won | 낙찰 | network, position, latencyMs | | lost | 해당 네트워크 실패 | network, position, reason | | noFill | 전체 실패 | — |

등록하지 않은 네트워크가 lost (reason: 'adapterNotRegistered')로 보이면, 해당 네트워크의 SDK 추가 + 등록이 빠진 것입니다(Android / iOS).

플레이스홀더 단위 ID로도 waterfall 로그(fetching→trying→lost/noFill)가 뜨면 워터폴 브리지가 정상입니다. 실광고는 각 네트워크의 실 단위 ID가 있어야 노출됩니다.


에러 처리 (AdError)

onFail로 전달되는 AdError{ code, message, statusCode? } 형태입니다. statusCode는 실패한 요청의 HTTP 상태(SDK가 보고할 때만, 없으면 0/undefined)입니다.

onFail={(error) => {
  console.warn(`[${error.code}] ${error.message}` +
    (error.statusCode ? ` (HTTP ${error.statusCode})` : ''));
}}

흔한 실패 상황:

| 상황 | 설명 | |---|---| | noFill | 노출 가능한 광고 없음. 테스트/플레이스홀더 단위에서 가장 흔함(정상 동작) | | 잘못된 광고 단위 ID | 존재하지 않거나 형식이 틀린 unit ID | | 네트워크 오류 | 통신 실패 — statusCode가 실릴 수 있음(HTTP 상태) | | 준비 전 표시 | onLoad 전에 present() 호출 | | 취소됨 | stop() 등으로 진행 중 요청 취소 |

테스트/플레이스홀더 단위에서는 노출 가능한 광고가 없어 onFail(noFill)이 흔히 발생합니다 — 이때 네이티브→JS 이벤트 브리지가 정상 동작하는 것입니다.


공통 API 요약

| 클래스 / 컴포넌트 | 용도 | |---|---| | Exelbid | 전역 — getSdkVersion · setLogLevel · requestTrackingAuthorization · getTrackingAuthorizationStatus · registerMediationNetworks | | ExelbidBanner | 임베디드 배너 (ref로 load/stop) | | ExelbidNativeAd + ExelbidNativeAd.* 슬롯 | 호스트 렌더링 네이티브 광고 | | ExelbidInterstitialAd / ExelbidVideoAd | 전면 / 비디오 (명령형 create/load/present) | | ExelbidMediatedBanner / …NativeAd / …InterstitialAd / …VideoAd | 미디에이션 광고 | | AdOptions | 타깃팅/테스트 옵션 | | AdError | 에러 ({ code, message, statusCode? }) | | WaterfallEvent · TrackingAuthorizationStatus · NativeAdData | 워터폴 이벤트 / ATT 상태 / 네이티브 자산 값 |


샘플 앱

example/에 RN 0.76 네이티브 호스트 셸(android/, ios/)이 포함된 전체 동작 예제가 있습니다. Home / Ads / Mediation 3탭에서 전 광고 유형을 실행해 볼 수 있습니다.

corepack enable                                   # 패키지매니저는 yarn 4
cd example && yarn install
cd ios && RCT_NEW_ARCH_ENABLED=1 pod install      # iOS
cd .. && yarn android --deviceId <adb id>         # Android (newArchEnabled 이미 true)

example/src/adUnitIds.ts의 플랫폼별 테스트 단위 ID를 본인 ExelBid 광고 단위로 바꾸세요. 상태가 failed: …(noFill)로 바뀌면 네이티브→JS 이벤트 브리지가 정상 동작하는 것입니다.


트러블슈팅

NativeExelbid.setLogLevel is not a function (런타임, 빌드는 통과) — codegen이 스펙을 생성하지 못했거나 New Arch가 꺼진 경우입니다. RCT_NEW_ARCH_ENABLED=1 pod install(iOS) / newArchEnabled=true(Android)를 확인하고, iOS는 Pods 재설치 후 클린 빌드하세요.

iOS 'ReactNativeExelbidSpec/ReactNativeExelbidSpec.h' file not found — codegen이 이 라이브러리를 건너뛴 경우입니다. pod install을 New Arch로 다시 돌리고, 그래도 안 되면 example/ios 기준으로 Pods·DerivedData를 지우고 재설치하세요.

iOS 앱이 켜자마자 종료(ATT)requestTrackingAuthorization()을 호출하는데 Info.plistNSUserTrackingUsageDescription이 없으면 iOS가 SIGKILL로 종료합니다. iOS 프로젝트 설정의 문구를 추가하세요.

미디에이션 NoClassDefFoundError(Android) / 컴파일 에러(iOS) — 어댑터는 등록했는데 해당 네트워크 SDK를 앱에 추가하지 않은 경우입니다. SDK 추가와 등록은 항상 같이 하세요 (미디에이션).

커스텀 폰트가 광고에 반영되지 않음 — 광고 글자는 OS가 그리므로 폰트를 iOS (UIAppFonts)·Android(res/font/) 앱에 등록해야 합니다 (커스텀 폰트).

onFail(noFill)만 계속 발생 — 테스트/플레이스홀더 단위 ID는 노출 가능한 광고가 없어 정상적으로 noFill이 납니다. 실 단위 ID로 바꿔 확인하세요.


부록: AdMob 옵션 (선택)

네이티브 광고 검사기 끄기 — AdMob 네이티브 광고를 테스트 기기에서 띄우면 Google Mobile Ads SDK가 광고 위에 검사기 오버레이를 표시합니다(테스트 광고 한정). 거슬리면:

  • AndroidAndroidManifest.xml<application> 안:

    <meta-data android:name="com.google.android.gms.ads.flag.NATIVE_AD_DEBUGGER_ENABLED"
      android:value="false" />
  • iOSInfo.plist:

    <key>GADNativeAdValidatorEnabled</key>
    <false/>

    참고: Android · iOS

전면/비디오 노치 채우기 (Android) — AdMob 전면·비디오는 GMA SDK의 AdActivity가 렌더링하며 기본적으로 상태바/노치 영역을 비워 둡니다. 그 영역까지 채우려면 호스트 앱 res/values/styles.xml에 풀스크린 테마를 정의하고 AndroidManifest.xml에서 tools:replaceAdActivity의 테마를 교체하세요.

<!-- res/values/styles.xml -->
<style name="AdCutoutTheme" parent="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
    <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
    <item name="android:windowFullscreen">true</item>
</style>
<!-- AndroidManifest.xml (<manifest>에 xmlns:tools 추가) -->
<activity android:name="com.google.android.gms.ads.AdActivity"
    android:theme="@style/AdCutoutTheme"
    tools:replace="android:theme" />

windowFullscreen은 상태바를 숨기고, windowLayoutInDisplayCutoutMode=shortEdges는 컷아웃(노치/펀치홀) 영역까지 진입합니다(API 28+, 하위는 무시). GMA SDK가 런타임에 인셋을 직접 적용해 위 테마가 무시되면 GMA SDK 업그레이드가 근본 해법입니다.


라이선스

LICENSE · CHANGELOG · CONTRIBUTING